Les Vertex Input Layouts

 

nvidia-quadro-graphics-card

Intro  :

Dans le cas basique, et en général, un vertex a pour attribut seulement sa position. Il existe cependant un mécanisme où il est possible de rajouter d’autres attributs comme : une normale, une couleur, des coordonnées de textures, etc… Je vais tenter de vous expliquer ce mécanisme.

Explications :

Avant tout, lors de l’étape de rendu Input Assembler (voir cet article) les données géométriques présentes dans la mémoire vidéo (composées de ces attributs) sont reçues puis traitées par le Vertex Shader.

Dans DirectX 10, la configuration de ces attributs s’opère sur l’initialisation d’une structure appelée D3D10_INPUT_ELEMENT_DESC.

Ici en tant qu’exemple cette structure (appelée dans le jargon anglais « Vertex Input Layout ») s’implémente ainsi :


// Défini un tableau de composition d'entrée (nommée Input Layout)
D3D10_INPUT_ELEMENT_DESC layout[] =
{
    { L"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};

Elle permet de définir en mémoire vidéo la façon dont sont agencées les vertex.
Remarque : La taille d’un vertex en mémoire équivaut à la taille de cette structure implémentée.

Une structure correspondante est aussi utilisée dans notre programme :

struct SimpleVertex
{
    D3DXVECTOR3 Pos;
};

 


 

Voici les variables de cette fameuse structure :

typedef struct D3D10_INPUT_ELEMENT_DESC {
  LPCSTR                     SemanticName;
  UINT                       SemanticIndex;
  DXGI_FORMAT                Format;
  UINT                       InputSlot;
  UINT                       AlignedByteOffset;
  D3D10_INPUT_CLASSIFICATION InputSlotClass;
  UINT                       InstanceDataStepRate;
} D3D10_INPUT_ELEMENT_DESC;

Paramètres :

SemanticName :

Nom de la « sémantique » de la variable.

Une « sémantique » est une chaîne de caractère qui indique dans quel but la variable doit être utilisée.

Ici son rôle est la position donc on met : « POSITION ».
On peut mettre aussi « NORMAL », « COLOR », « TEXCOORD », etc…

SemanticIndex :

C’est un index (un nombre entier) qui désigne le numéro d’un sémantique qui a déjà été déclaré.

Par exemple, si on déclare deux textures avec sémantique « TEXTURE », on mettra comme SemanticIndex pour le premier champ « 0 » et pour le deuxième champ « 1 ».

Format :

Le format définit le type de données qui sera utilisé pour cet élément.

Par exemple, pour DXGI_FORMAT_R32G32B32_FLOAT : il s’agit d’un attribut avec comme type un flottant et 8 octets alloués par couleur.

InputSlot :

Dans l’étape Input Assembler on peut définir jusqu’à 16 Vertex buffer qui peuvent être utilisés par la carte graphique avant d’être envoyés au Vertex Shader.

Ici on spécifie quel Vertex buffer doit être utilisé par cet attribut.

AlignedByteOffset :

Ce champ dit à la carte graphique quel l’emplacement mémoire pour commencer à aller chercher les données pour cet attribut.
Indiquez D3D10_APPEND_ALIGNED_ELEMENT pour que cet emplacement soit défini de manière automatique.

InputSlotClass :

Cet attribut est utilisé pour gérer l’Instancing. Voir cet article.

InstanceDataStepRate :

Cet attribut est utilisé pour gérer l’Instancing. Voir cet article.

Au final on peut créer cette composition de vertex par le biais de cette méthode : ID3D10Device::CreateInputLayout.

 


 

 

Résumé :

La déclaration d’une structure / composition de vertex permet à la carte graphique de regrouper les informations utiles concernant les vertex en mémoire vidéo avant d’être traités et envoyés au Vertex Shader.

Références :

– http://msdn.developpez.com/direct3d/10/tutoriels/base/

– https://msdn.microsoft.com/en-us/library/windows/desktop/bb205316%28v=vs.85%29.aspx

– https://msdn.microsoft.com/en-us/library/windows/desktop/bb509647%28v=vs.85%29.aspx

Introduction à DirectX 10 – Texturing – partie 3

directx9c

Intro :

Dans cette troisième partie, nous allons apprendre comment ajouter une texture à un petit cube auto généré.

Prérequis :

Avoir suivi la deuxième partie de ce tutoriel.

Troisième partie :

Afficher un petit cube avec une texture appliquée dessus.

Explications :

Dans ce tutoriel nous allons utiliser cette structure de vertex :

struct SimpleVertex
{
    D3DXVECTOR3 Pos;
    D3DXVECTOR2 Tex;
};

La deuxième variable indique que l’on va utiliser les coordonnées de la texture appliqués au cube.

Rajoutez dans votre fichier D3D10Renderer.h :


/* Variables HLSL des matrix.
   Ces variables seront utilisées et envoyées
   au vertex shader HLSL. */
ID3D10EffectMatrixVariable* m_pWorldVariable;
ID3D10EffectMatrixVariable* m_pViewVariable;
ID3D10EffectMatrixVariable* m_pProjectionVariable;

// Variable de la texture
ID3D10EffectShaderResourceVariable* m_pDiffuseVariable;

// Les matrix pour afficher le rendu
D3DXMATRIX m_worldMatrix;
D3DXMATRIX m_viewMatrix;
D3DXMATRIX m_projectionMatrix;

ID3D10ShaderResourceView* m_pTextureRV;

 

Voici le fichier effet HLSL utilisé – partie3.fx :

//--------------------------------------------------------------------------------------
// Constant Buffer Variables
//--------------------------------------------------------------------------------------
Texture2D txDiffuse;
SamplerState samLinear
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};

cbuffer cbNeverChanges
{
    matrix View;
};

cbuffer cbChangeOnResize
{
    matrix Projection;
};

cbuffer cbChangesEveryFrame
{
    matrix World;
};

struct VS_INPUT
{
    float4 Pos : POSITION;
    float2 Tex : TEXCOORD;
};

struct PS_INPUT
{
    float4 Pos : SV_POSITION;
    float2 Tex : TEXCOORD0;
};

//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
PS_INPUT VS( VS_INPUT input )
{
    PS_INPUT output = (PS_INPUT)0;
    output.Pos = mul( input.Pos, World );
    output.Pos = mul( output.Pos, View );
    output.Pos = mul( output.Pos, Projection );
    output.Tex = input.Tex;
    
    return output;
}

//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float4 PS( PS_INPUT input) : SV_Target
{
    return txDiffuse.Sample( samLinear, input.Tex );
}

//--------------------------------------------------------------------------------------
technique10 Render
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS() ) );
    }
}

 

Dans le fichier D3D10Renderer.cpp à la fonction Initialize(bool bFullScreen) :

/******************** Partie 2 & 3 ********************/

DWORD dwShaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;

#if defined( DEBUG ) || defined( _DEBUG )
// Permet d'afficher les éventuelles erreurs de la compilation d'un shader
dwShaderFlags |= D3D10_SHADER_DEBUG;
#endif

// Pour la création du shader, on le compile
hr = D3DX10CreateEffectFromFile(L"Partie3.fx", NULL, NULL, "fx_4_0", dwShaderFlags, 0, m_pd3dDevice, NULL,
                                NULL, &m_pEffect, NULL, NULL);
if (FAILED(hr))
    return false;

// On acquiert la technique du shader HLSL
m_pTechnique = m_pEffect->GetTechniqueByName("Render");

/* Voici les variables matrix utilisées par le shader HLSL */
m_pWorldVariable = m_pEffect->GetVariableByName("World")->AsMatrix();
m_pViewVariable = m_pEffect->GetVariableByName("View")->AsMatrix();
m_pProjectionVariable = m_pEffect->GetVariableByName("Projection")->AsMatrix();
m_pMeshColorVariable = m_pEffect->GetVariableByName("vMeshColor")->AsVector();
m_pDiffuseVariable = m_pEffect->GetVariableByName("txDiffuse")->AsShaderResource();

/******************************************************/

Voir cet article si vous ne comprenez pas à quoi sert les matrices World, View et Projection.

On définit la nouvelle structure d’un vertex :


D3D10_INPUT_ELEMENT_DESC layout[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};
UINT numElements = sizeof( layout ) / sizeof( layout[0] );

D3D10_PASS_DESC PassDesc;
m_pTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);
hr = m_pd3dDevice->CreateInputLayout(layout, numElements, PassDesc.pIAInputSignature,
                                     PassDesc.IAInputSignatureSize, &m_pVertexLayout);
if(FAILED(hr))
    return false;

m_pd3dDevice->IASetInputLayout(m_pVertexLayout);

/******************************************************/

On définit les informations de vertex et index buffers d’un cube :

// On créé le vertex buffer d'un cube
SimpleVertex vertices[] =
{
    { D3DXVECTOR3( -1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 0.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 1.0f, 1.0f ) },
    { D3DXVECTOR3( -1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ) },

    { D3DXVECTOR3( -1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 0.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 1.0f, 1.0f ) },
    { D3DXVECTOR3( -1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ) },

    { D3DXVECTOR3( -1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 0.0f ) },
    { D3DXVECTOR3( -1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ) },
    { D3DXVECTOR3( -1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 1.0f ) },
    { D3DXVECTOR3( -1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ) },

    { D3DXVECTOR3( 1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 1.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ) },

    { D3DXVECTOR3( -1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 0.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 1.0f ) },
    { D3DXVECTOR3( -1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 0.0f, 1.0f ) },

    { D3DXVECTOR3( -1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 1.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 1.0f, 1.0f ) },
    { D3DXVECTOR3( -1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ) },
};

// On créé l'index buffer de ce cube
DWORD indices[] =
{
    3,1,0,
    2,1,3,

    6,4,5,
    7,4,6,

    11,9,8,
    10,9,11,

    14,12,13,
    15,12,14,

    19,17,16,
    18,17,19,

    22,20,21,
    23,20,22
};

Voir cet article si vous ne comprenez pas à quoi sert les vertex buffer et index buffer.

On implémente les vertex et index buffers :

/********** Vertex buffer **********/

D3D10_BUFFER_DESC bd;
bd.Usage = D3D10_USAGE_DEFAULT;
// Taille d'une entrée vertex
bd.ByteWidth = sizeof(SimpleVertex) * 24;
bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;
bd.MiscFlags = 0;
D3D10_SUBRESOURCE_DATA InitData;
InitData.pSysMem = vertices;
hr = m_pd3dDevice->CreateBuffer(&bd, &InitData, &m_pVertexBuffer);
if(FAILED(hr))
    return false;

UINT stride = sizeof(SimpleVertex);
UINT offset = 0;
m_pd3dDevice->IASetVertexBuffers(0, 1, &m_pVertexBuffer, &stride, &offset);

/********** Index buffer **********/

bd.Usage = D3D10_USAGE_DEFAULT;
// Taille d'une entrée index
bd.ByteWidth = sizeof( DWORD ) * 36;
bd.BindFlags = D3D10_BIND_INDEX_BUFFER;
bd.CPUAccessFlags = 0;
bd.MiscFlags = 0;
InitData.pSysMem = indices;
hr = m_pd3dDevice->CreateBuffer(&bd, &InitData, &m_pIndexBuffer);
if(FAILED(hr))
    return false;

// Set index buffer
m_pd3dDevice->IASetIndexBuffer( m_pIndexBuffer, DXGI_FORMAT_R32_UINT, 0 );

/* On définit le type de primitive */
m_pd3dDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

/******************************************************/

 

On charge la texture qui sera utilisée :

// On charge la texture
hr = D3DX10CreateShaderResourceViewFromFile(m_pd3dDevice, L"seafloor.dds", NULL, NULL, &m_pTextureRV, NULL);
if(FAILED(hr))
    return false;

 

On initialise les matrices et la texture :

D3DXMatrixIdentity(&m_worldMatrix);

D3DXVECTOR3 Eye(0.0f, 3.0f, -6.0f);
D3DXVECTOR3 At(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 Up(0.0f, 1.0f, 0.0f);

D3DXMatrixLookAtLH(&m_viewMatrix, &Eye, &At, &Up);

// Initialize the projection matrix
D3DXMatrixPerspectiveFovLH( &m_projectionMatrix, ( float )D3DX_PI * 0.25f, width / ( FLOAT )height, 0.1f, 100.0f );

// Update Variables that never change
m_pViewVariable->SetMatrix( ( float* )&m_viewMatrix );
m_pProjectionVariable->SetMatrix( ( float* )&m_projectionMatrix );
m_pDiffuseVariable->SetResource( m_pTextureRV );

 

Résumé :

Au final nous avons affiché un petit cube texturé.

partie3

Voici l’archive du code complet pour cette partie :  DirectX 10 Tutoriel – Partie 3.zip

Introduction à DirectX 10 – Rendu basique – partie 2

directx9c

Intro :

Dans cette deuxième partie, nous allons utiliser un shader basique pour afficher un triangle et afficher du texte à l’écran.

Prérequis :

Avoir suivi la première partie de ce tutoriel.
Savoir le fonctionnement d’un shader HLSL : pour cela il faut suivre le tutoriel sur le HLSL.

Deuxième partie :

Afficher un triangle et du texte à l’écran.

Explications :

Nous allons utiliser ce programme shader nommé Partie2.fx :


//----------------------------------------------------------
// Partie2.fx
//----------------------------------------------------------

struct VS_OUTPUT
{
    float4 Pos : SV_POSITION;
    float4 Color : COLOR0;
};

//----------------------------------------------------------
// Vertex Shader
//----------------------------------------------------------
VS_OUTPUT VS( float4 Pos : POSITION, float4 Color : COLOR )
{
    VS_OUTPUT output = (VS_OUTPUT)0;
    
    output.Pos = Pos;
    output.Color = Color;
    
    return output;    
}

//----------------------------------------------------------
// Pixel Shader
//----------------------------------------------------------
float4 PS( VS_OUTPUT input ) : SV_Target
{
    return input.Color;
}

//----------------------------------------------------------
technique10 Render
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS() ) );
    }
}

Si vous ne comprenez ce bout de code, voir l’article sur le HLSL.

Pensez à mettre ce fichier dans le répertoire de votre programme ! Vous aurez une erreur sinon.

Nous allons afficher un triangle à l’écran. Pour cela il faut que DirectX comprenne comment
sont agencés les vertices dans la mémoire vidéo.

Donc ici on déclare que toutes les vertices utilisées sont composées d’une variable position et d’une variable couleur. Cette structure suivante est utilisée par notre application.


struct SimpleVertex
{
    D3DXVECTOR3 Pos;
    D3DXVECTOR4 Color;
};

Et voilà un schéma des données vertices. Cette structure suivante est utilisée par DirectX.
Voir cet article pour mieux comprendre le mécanisme de cette structure.

D3D10_INPUT_ELEMENT_DESC layout[] =
{
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0},
};
UINT numElements = sizeof(layout) / sizeof(layout[0]);

 

On rajoute à la fonction Initialize du fichier D3D10Renderer.cpp donné dans la partie 1
du tutoriel :

/******************** Partie 2 ********************/

DWORD dwShaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;

#if defined( DEBUG ) || defined( _DEBUG )
// Permet d'afficher les éventuelles erreurs de la compilation d'un shader
dwShaderFlags |= D3D10_SHADER_DEBUG;
#endif

// Pour la création du shader, on le compile
hr = D3DX10CreateEffectFromFile(L"Partie2.fx", NULL, NULL, "fx_4_0", dwShaderFlags, 0, m_pd3dDevice, NULL,
                                NULL, &m_pEffect, NULL, NULL);
if (FAILED(hr))
    return false;

// On acquiert la technique du shader HLSL
m_pTechnique = m_pEffect->GetTechniqueByName("Render");

// On définit la structure d'un vertex
D3D10_INPUT_ELEMENT_DESC layout[] =
{
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0},
};
UINT numElements = sizeof(layout) / sizeof(layout[0]);

D3D10_PASS_DESC PassDesc;
m_pTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);
hr = m_pd3dDevice->CreateInputLayout(layout, numElements, PassDesc.pIAInputSignature,
                                      PassDesc.IAInputSignatureSize, &m_pVertexLayout);
if(FAILED(hr))
    return false;

m_pd3dDevice->IASetInputLayout(m_pVertexLayout);

// On définit les vertices
SimpleVertex vertices[] =
{
    D3DXVECTOR3( 0.0f, 0.5f, 0.5f ), D3DXVECTOR4( 0.0f, 1.0f, 1.0f, 1.0f ),
    D3DXVECTOR3( 0.5f, -0.5f, 0.5f ), D3DXVECTOR4( 0.0f, 0.0f, 1.0f, 1.0f ),
    D3DXVECTOR3( -0.5f, -0.5f, 0.5f ), D3DXVECTOR4( 1.0f, 0.0f, 1.0f, 1.0f ),
};
// On implémente le vertex buffer
D3D10_BUFFER_DESC bd;
bd.Usage = D3D10_USAGE_DEFAULT;
bd.ByteWidth = sizeof(SimpleVertex) * 6;
bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;
bd.MiscFlags = 0;
D3D10_SUBRESOURCE_DATA InitData;
InitData.pSysMem = vertices;
hr = m_pd3dDevice->CreateBuffer(&bd, &InitData, &m_pVertexBuffer);
if(FAILED(hr))
    return false;

// On définit le vertex buffer
UINT stride = sizeof(SimpleVertex);
// Décalage des informations du vertex buffer
UINT offset = 0;
m_pd3dDevice->IASetVertexBuffers(0, 1, &m_pVertexBuffer, &stride, &offset);

// On définit le type de primitive
m_pd3dDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

/******** Font ********/

D3DX10CreateSprite(m_pd3dDevice, 0, &m_pSprite);

D3DX10_FONT_DESC fd;
fd.Height = 30;
fd.Width = 18;
fd.Weight = 0;
fd.MipLevels = 4;
fd.Italic = false;
fd.CharSet = OUT_DEFAULT_PRECIS;
fd.Quality = DEFAULT_QUALITY;
fd.PitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
wcscpy(fd.FaceName, L"Impact");

D3DX10CreateFontIndirect(m_pd3dDevice, &fd, &m_pFont);

 

La fonction de rendu va afficher un triangle et un texte comme convenu :

void D3D10Renderer::Render()
{
    static float afClearColor[4] = {0.0f, 0.125f, 0.3f, 1.0f}; 

    static D3DXCOLOR color(0.0f, 1.0f, 0.3f, 1.0f);
    static RECT rectangle = {10, 10, 0, 0};

    // Efface le backbuffer
    m_pd3dDevice->ClearRenderTargetView(m_pRenderTargetView, afClearColor);

    // Commence par afficher du texte
    m_pSprite->Begin(D3DX10_SPRITE_SAVE_STATE);
    m_pFont->DrawText(m_pSprite, L"Hello World!", -1, &rectangle, DT_NOCLIP, color);
    m_pSprite->End();
    
    // Rendu d'un triangle
    D3D10_TECHNIQUE_DESC techDesc;
    m_pTechnique->GetDesc(&techDesc);
    for (unsigned int p = 0; p < techDesc.Passes; ++p)
    {
        m_pTechnique->GetPassByIndex(p)->Apply(0);
        // On rend 3 vertex
        m_pd3dDevice->Draw(3, 0);
    }

    m_pSwapChain->Present(0, 0);
}

N’oubliez pas d’ajouter dans votre fichier System.h :

ID3DX10Font* m_pFont;
ID3DX10Sprite* m_pSprite;

Résumé :

Au final nous avons affiché un triangle multi-colorisé et affiché un petit texte :

triangle

Voici l’archive du code complet pour cette partie : DirectX 10 Tutoriel – Partie 2.zip

Index buffer et Vertex buffer

AMD_6990

Intro :

Les notions d’Index buffer et de Vertex buffer sont fondamentales dans la compréhension des étapes de rendu 3D de modèles / meshes. Ils sont appelés sommets et d’indices en français.

Prérequis :

– Aucun

Vertex :

D’abord, qu’est-ce qu’un vertex ?

On peut imaginer un vertex comme un simple point dans un espace 3D. Ce sont ces vertex qui nous permettront de construire les primitives de base (points, lignes, triangles). Et c’est ces primitives nous permettrons de construire des modèles / objets dans le rendu 3D.

Index / Indice :

Qu’est-ce qu’un index (indice) ? Il faut noter que ce sont juste des nombres que l’on va utiliser par groupe de 3 pour représenter l’ordre d’utilisation d’un triangle.

Autrement dit, c’est un triplet d’index qui sert à indiquer à la carte graphique dans quel ordre sont utilisés les vertex affichés par celle-ci. Ces index peuvent servir à réutiliser les vertices précédemment déclarés.

Vertex buffer :

Un Vertex buffer est simplement une interface stockant les informations sur les vertices d’un modèle du jeu. C’est un tableau situé dans la mémoire de la carte graphique.

On peut stocker dans un vertex buffer des informations : comme la position, les vecteurs normales, les coordonnées d’une texture ou encore même la couleur, voir même n’importe quoi…

Index buffer :

De même que le Vertex buffer,  c’est une interface qui stocke les indices présents dans un tableau contigu.


Déclaration de vertex :

Pour que la carte graphique comprenne comment sont agencées les données d’un vertex buffer, il faut déclarer une structure correspondante. On appel cela une « déclaration de vertex ».

 


 

Avant tout il faut définir une structure correspondante et représentative du type de Vertex :

struct SimpleVertex
{
    D3DXVECTOR3 Position;
    D3DXVECTOR3 Color;
};


Code C++ d’implémentation d’un Vertex Buffer statique :

D3D10_BUFFER_DESC bufferDesc;

bufferDesc.Usage            = D3D10_USAGE_DEFAULT;
bufferDesc.ByteWidth        = sizeof( SimpleVertex ) * 3;
bufferDesc.BindFlags        = D3D10_BIND_VERTEX_BUFFER;
bufferDesc.CPUAccessFlags   = 0;
bufferDesc.MiscFlags        = 0;

ID3D10Buffer* pVertexBuffer = nullptr;

SimpleVertex vertices[] =
{
    D3DXVECTOR3( 0.0f, 0.5f, 0.5f ),
    D3DXVECTOR3( 0.0f, 0.0f, 0.5f ),
    D3DXVECTOR3( 0.5f, -0.5f, 0.5f ),
    D3DXVECTOR3( 0.5f, 0.0f, 0.0f ),
    D3DXVECTOR3( -0.5f, -0.5f, 0.5f ),
    D3DXVECTOR3( 0.0f, 0.5f, 0.0f ),
};

D3D10_SUBRESOURCE_DATA InitData;
InitData.pSysMem = vertices;
InitData.SysMemPitch = 0;
InitData.SysMemSlicePitch = 0;

D3D10_RENDERER->GetDevice()->CreateBuffer( &bufferDesc, &InitData, &pVertexBuffer);


Code C++ d’implémentation d’un Index Buffer :

ID3D10Buffer pIndexBuffer = nullptr;

unsigned int indices[] = { 0, 1, 2 };

D3D10_BUFFER_DESC bufferDesc;
bufferDesc.Usage           = D3D10_USAGE_DEFAULT;
bufferDesc.ByteWidth       = sizeof( unsigned int ) * 3;
bufferDesc.BindFlags       = D3D10_BIND_INDEX_BUFFER;
bufferDesc.CPUAccessFlags  = 0;
bufferDesc.MiscFlags       = 0;

D3D10_SUBRESOURCE_DATA InitData;

InitData.pSysMem = indices;
InitData.SysMemPitch = 0;
InitData.SysMemSlicePitch = 0;

D3D10_RENDERER->GetDevice()->CreateBuffer(&bufferDesc, &InitData, &pIndexBuffer);


Résumé :

Pour afficher un modèle dans un rendu 3D on a besoin de définir comment il est représenté : c’est-à-dire par deux tableaux d’informations de Vertex et d’Indices et une déclaration de vertex.

 

Introduction au moteur 3D Irrlicht – partie 1

I073

 

Intro :

Irrlicht 3D est un moteur 3D gratuit et open-source écrit en C++.
Son utilisation est facilité par le petit nombre de méthodes (fonctions) qu’il possède par rapport à Ogre3D qui quant à lui comporte beaucoup plus de fonctions. Irrlicht 3D est donc plutôt simple à utiliser.

Il gère aussi bien les APIs DirectX que OpenGL. Il peut être aussi utilisé sur Linux, Windows et OS X.

Version d’Irrlicht 3D utilisée : 1.8.1

Prérequis :

– Savoir lire les rudiments d’un programme C++ (pointeur, classe, etc…)
– Savoir configurer Visual Studio C++ Express (voir cet article)

Explications :

Il vous faut comprendre comment configurer les répertoires de dépendance dans Visual C++.

Télécharger Irrlicht :

http://irrlicht.sourceforge.net/downloads.html

Ce qu’il y a dans le repertoire extrait :

bin : les bibliothèques dynamiques et les binaires des exemples du SDK

include : les principaux headers du code source

lib : les bibliothèques statiques

Ce qu’il faut faire :

Dans le répertoire bin extrait : il vous suffit de choisir le dossier correspondant à votre configuration et de copier le fichier .dll quelque part.
Vous devrez le mettre dans le même dossier que vos fichiers exécutables (.exe) pour que ceux-ci fonctionnent.

Il faut configurer votre IDE (en l’occurence Visual Studio C++)
en paramétrant les dossiers include / et lib (suivre ce tutoriel pour le configurer [config_vc++]

Mettre dans votre fichier .cpp principal :

 


 

Exemple complet :

 // Définition de la bibliothèque 
Irrlicht.lib#pragma comment(lib, "Irrlicht.lib")

 // Déclaration du fichier d'en-tête d'Irrlicht
#include <irrlicht.h>

// Déclaration du namespace (espace de nom) d'Irrlicht
using namespace irr; 

int main()
{
    // Créée le device qui va gérer le moteur 3D
    IrrlichtDevice* device = createDevice(video::EDT_DIRECT3D8,
        core::dimension2d<u32>(640,480));

    // Système qui permet d'afficher du rendu à l'écran
    video::IVideoDriver* driver = device->getVideoDriver();
    scene::ISceneManager* scenemgr = device->getSceneManager();

    // On spécifie le titre de la fenêtre
    device->setWindowCaption(L"Hello World!");

    // On charge un fichier modèle .md2
    scene::ISceneNode* node = scenemgr->addAnimatedMeshSceneNode(
        scenemgr->getMesh("quake2model.md2"));

    // On ajoute à ce modèle, une texture et on désactive les lumières
    if (node)
    {
        node->setMaterialTexture(0, driver->getTexture("texture.bmp"));
        node->setMaterialFlag(video::EMF_LIGHTING, false);
    }

    // On ajoute une caméra de type FPS (Vue à la première personne)
    scenemgr->addCameraSceneNodeFPS();

    // On affiche le rendu !
    while (device->run() && driver)
    {
        driver->beginScene(true, true, video::SColor(255,0,0,255));
        scenemgr->drawAll();
        driver->endScene();
    }

    // On efface le device
    device->drop();
    return 0;
}

 


Fonction createDevice (…) :


irr::IrrlichtDevice* device =
             irr::createDevice(irr::video::EDT_OPENGL,
             irr::core::dimension2d<irr::u32>(800,600),
             32, false, true, false, 0);

 

Explication du bout de code :

Les arguments passés en paramètre sont dans l’ordre :

device  : c’est un pointeur sur l’objet returné par irr:createDevice(…)

deviceType : désigne l’API graphique avec laquelle va s’interfacer Irrlicht. Nous utiliserons OpenGL tout au long du tutoriel, mais on peut aussi choisir Direct3D par exemple en mettant la valeur EDT_DIRECT3D9.

windowSize : la taille de la fenêtre. Tout est dit ! Avec le code indiqué plus haut, on crée une
fenêtre de 800 par 600 pixels.

bits : détermine le nombre de bits par pixels.

fullscreen : un booléen qui indique si on veut faire du plein écran ou pas. Pour la valeur true l’application
sera en plein écran, et pour false la fenêtre sera de la taille définie plus haut.

stencilbuffer : un autre booléen qui indique lui si on active le stencil buffer ou pas. Il est actif pour true.

vsync : toujours un booléen. Celui-ci sert à activer la synchronisation verticale. Pas de mystère, elle est active pour true.

receiver : permet d’associer un capteur d’événements au device (0 signifie qu’on n’associe rien). Nous verrons de quoi il s’agit dans un prochain chapitre.

Résumé :

Dans cette première partie nous avons appris comment se servir de Irrlicht 3D.

Références :

– http://irrlicht.sourceforge.net/features/
– http://openclassrooms.com/courses/3d-temps-reel-avec-irrlicht
– http://irrlicht.sourceforge.net/docu/example001.html

Une console Quake-like colorisée

quake-like_console

Intro :

Parfois dans un jeu on a besoin de configurer ou de paramétrer le gameplay ou les mécanismes du jeu directement dans le programme.

Prérequis :

– Savoir un peu utiliser DirectX 10

– Savoir lire du C++

Explications :

On peut entrer et exécuter des commandes (toutes très utiles) pour modifier les variables ou les comportements du jeu. On peut aussi par ce biais afficher des informations système ou des informations sur le comportement du jeu.

Nous allons implémenter cette console dans DirectX 10. Mais vous pouvez aussi l’implémenter dans votre propre application à partir du code expliqué.

Cette console présente les fonctionnalités suivantes :

– éléments de texte en couleur
– historique de commande
– commandes basiques
– image de fond
– correction / suppression de caractère de la ligne de commande

Prérequis :

– Savoir lire du C++

– Savoir initialiser DirectX 10

Explications :

Voici le fichier Console.h :

#ifndef CONSOLE_H
#define CONSOLE_H

#include <string>
#include <vector>
#include <list>
#include <d3dx10math.h>

#include "Singleton.h"
#include "Sprite2D.h"
#include "InputManager.h"

class KeyEvent;
class Line;
class Command;
class DataParameters;

struct MessageType
{
    enum Type {MSG_SYSTEM, MSG_WARNING, MSG_ERROR, MSG_DEBUG, MSG_GAME, MSG_COMMAND, MSG_BLANK};

    // Type
    static std::string GetMessageType(MessageType::Type type);

    // Couleur
    static D3DXCOLOR GetMessageColour(MessageType::Type type);
};

/* Un ensemble de mots */
class TextElement
{
public:
    TextElement(std::wstring sTextElementText, D3DXCOLOR colour, bool bBlinking);
    virtual ~TextElement();

    int GetLength();

    std::wstring GetText();

    D3DXCOLOR GetColour();

    void SetColour(D3DXCOLOR colour);

    bool IsBlinking();

private:
    std::wstring m_sTextElement;

    bool m_bBlinking;
    D3DXCOLOR m_colour;    
};

/* Un ensemble de TextElement */
class Line
{
public:
    Line(MessageType::Type type);
    virtual ~Line();

    void AddTextElement(std::wstring sTextElement, D3DXCOLOR colour, bool bBlinking);
    void AddTextElementSolo(std::wstring sTextElement, D3DXCOLOR colour, bool bBlinking);

    void GetTextElements(std::vector<TextElement*>& elems);

    unsigned int GetTextElementsCount();

    MessageType::Type GetType();

private:
    void ParseLines(std::wstring sText, std::vector<std::wstring>& moreLines);

private:
    std::vector<TextElement*> m_textElements;

    MessageType::Type m_type;

    unsigned int m_iLastElementLength;
};

/* Le petit texte de saisie en bas à gauche de la console */
class CommandLinePrompt
{
public:
    CommandLinePrompt();
    virtual ~CommandLinePrompt();

    void AppendCommandLineText(const std::wstring sText);

    void SetText(const std::wstring sText);

    void Clear();

    bool HasText();

    // Retourne le texte du prompt
    const std::wstring GetPromptText();
    // Retourne le texte de la ligne de commande
    const std::wstring GetCommandLineText();

    void Render();

    void SetArrowTransparency(float fValue);
    void SetArrowPosition(float x, float y);
    void SetArrowSize(unsigned int iArrowWidth, unsigned int iArrowHeight);

    void MoveCursorToLeft();
    void MoveCursorToRight();

    unsigned int GetCursorPos();

    void Update(float fYPosition);

    float GetCommandLengthPosition();

    void ResetCursorPos();

private:
    std::wstring m_sPromptText;
    std::wstring m_sCommandLineText;

    Sprite2D* m_pArrowSprite;

    int m_iCursorPos;
};

/* L'historique des commandes */
class CommandsHistory
{
public:
    CommandsHistory();
    virtual ~CommandsHistory();

    bool Empty();

    const std::wstring GetNext();
    const std::wstring GetPrevious();

    void Clear();

    void AddCommand(const std::wstring sCommandName);

private:
    std::list<std::wstring> m_history;

    std::wstring m_sLastCommand;
};

class Console : public Singleton<Console>, public KeyListener
{
    friend class Line;
    friend class CommandLinePrompt;

public:
    enum ConsoleState {MOVING_UP, MOVING_DOWN, HIDDEN, SHOWN};
    enum TransparencyState {FADE_IN, FADE_OUT};

    Console();
    virtual ~Console();
    
    void Initialize(); 

    // Fonctions de conversion de chaînes de caractères
    static std::string ToString(const std::wstring& sText);
    static std::wstring ToWString(const std::string& sText);

    void Render(float fTimeElapsedSinceLastFrame);

    void SetState(ConsoleState state);

    /** Méthodes d'affichage **/
    void Print(Line* pLine);
    void Print(const std::wstring& sMessage, D3DXCOLOR colour, MessageType::Type type);
    void Print(const std::wstring& sMessage, MessageType::Type type);
    void Print(const std::wstring& sMessage, D3DXCOLOR colour);
    void Print(const std::wstring& sMessage);
    void Print(const std::string& sMessage);

    // Ajoute une ligne vide
    void AddBlankLine();

    bool IsVisible();

    void ToggleState();

    void AddToHistory(const std::wstring& cmd);
    bool IsCharAuthorized(const wchar_t key);

    // Ajoute une ligne dans la console
    void AddLine(Line* pLine); 

    // Ces méthodes permettent de défiler la console
    void IncreaseScrollingPosition();
    void DescreaseScrollingPosition();
    void ResetScrollingPosition();

    // Ajoute les éléments qui qui changent de transparence
    void AddBlinkingTextElement(TextElement* pTextElement);
    void RemoveAllBlinkingTextElements();

    void Clear();

    virtual void SoloKeyPressed(const KeyEvent &arg);
    virtual void SoloKeyReleased(const KeyEvent &arg);

    virtual void RepetitiveKeyPressed(const KeyEvent &arg);
    virtual void RepetitiveKeyReleased(const KeyEvent &arg);

    unsigned int GetCharactWidth();

private:
    unsigned int GetWidth();
    float GetStringLength(std::wstring sText);
    void UpdateSprites(float fTimeElapsedSinceLastFrame);
    void CreateTextFont();
    void UpdateCommandLineText();
    void LoadDefaultCommands();
    void TabComplete();
    void GetCommandLineArguments(const std::wstring& sCommandLine, DataParameters& param);
    bool TextIsOutOfWidth(const std::wstring sText);

    void DeletePreviousCharacter();
    void DeleteNextCharacter();

private:
    // Les lignes affichées
    std::vector<Line*> m_lines;

    // Hauteur de la console
    float m_fConsoleHeight;
    // Largeur de la console
    float m_fConsoleWidth;
    
    // Hauteur totale de la console en prenant en compte le prompt
    unsigned int m_iTotalConsoleHeight;

    // Largeur d'un caractère
    float m_fCharWidth;
    // Hauteur d'un caractère
    float m_fCharHeight;

    // Le nombre maximum de lignes affichées à la fois
    unsigned int m_iMaxLinesDisplayed;
    // Le nombre maximul de caractères présent sur une ligne
    unsigned int m_iMaxCharOnLine;

    std::vector<TextElement*> m_blinkingTextElements;

    TransparencyState m_elemState;
       ConsoleState m_state;

    int m_iLinesStart;
    int m_iLinesOffset;

    ID3DX10Font* m_pFont;

    ID3DX10Sprite* m_pConsoleTextSprite;

    Sprite2D* m_pBackgroudImage;
    Sprite2D* m_pBorderImage;

    // Chaîne de tous les carcatères autorisés
    std::wstring m_aLegalChars;

    CommandLinePrompt* m_pPrompt;
    CommandsHistory* m_pHistory;

    float m_fScrollOffset;

    std::list<std::wstring> m_tabHistory;
};

#endif

 

Voici le fichier Console.cpp :

//----------------------------------------------------
// Auteur : Clément Profit
// Date de création : Juillet 2010
// Description : Une console quake-like avec commandes et appel
// d'events
//----------------------------------------------------

#include "InputManager.h"
#include "Console.h"
#include "Defines.h"

#include "Commands.h"
#include "DataParameters.h"
#include "D3D10Renderer.h"

template<> Console* Singleton<Console>::ms_instance = nullptr;

/********** MessageType **********/

std::string MessageType::GetMessageType(MessageType::Type type)
{
    std::string messageType = "";

    switch (type)
    {
        case MSG_SYSTEM: messageType = "[SYSTEM]"; break;
        case MSG_WARNING: messageType = "[WARNING]"; break;
        case MSG_ERROR: messageType = "[SYS_ERROR]"; break;
        case MSG_DEBUG: messageType = "[SYS_DEBUG]"; break;
        case MSG_GAME: messageType = "[GAME]"; break;
        case MSG_COMMAND: messageType = "[COMMAND]"; break;
        case MSG_BLANK: messageType = ""; break;
    }

    return messageType;
}

D3DXCOLOR MessageType::GetMessageColour(MessageType::Type type)
{
    D3DXCOLOR messageColour;;

    switch (type)
    {
        case MSG_SYSTEM: messageColour = D3DXCOLOR(0.8f, 0.9f, 1.0f, 1.0f); break;
        case MSG_WARNING: messageColour = D3DXCOLOR(0.7f, 0.8f, 1.0f, 1.0f); break;
        case MSG_ERROR: messageColour = D3DXCOLOR(0.2f, 0.4f, 1.0f, 1.0f); break;
        case MSG_DEBUG: messageColour = D3DXCOLOR(0.3f, 0.5f, 1.0f, 1.0f); break;
        case MSG_GAME: messageColour = D3DXCOLOR(0.1f, 0.8f, 1.0f, 1.0f); break;
        case MSG_COMMAND: messageColour = D3DXCOLOR(0.4f, 0.8f, 1.0f, 1.0f); break;
        case MSG_BLANK: messageColour = D3DXCOLOR(1.0, 1.0f, 1.0f, 1.0f); break;
    }

    return messageColour;
}

/********** TextElement **********/

TextElement::TextElement(std::wstring sTextElementText, D3DXCOLOR colour, bool bBlinking) :
m_sTextElement(sTextElementText),
m_bBlinking(bBlinking),
m_colour(colour)
{
}

TextElement::~TextElement()
{
}

int TextElement::GetLength()
{
    return m_sTextElement.length();
}

std::wstring TextElement::GetText()
{
    return m_sTextElement;
}

D3DXCOLOR TextElement::GetColour()
{
    return m_colour;
}

void TextElement::SetColour(D3DXCOLOR colour)
{
    m_colour = colour;
}

bool TextElement::IsBlinking()
{
    return m_bBlinking;
}

/********** Line **********/

Line::Line(MessageType::Type type) :
m_type(type),
m_iLastElementLength(0)
{
    // Ajoute le type de message (par ex [Game]) au début de la ligne
    AddTextElementSolo(Console::ToWString(MessageType::GetMessageType(m_type)), MessageType::GetMessageColour(m_type), false);
}

Line::~Line()
{
    for (unsigned int i = 0; i < m_textElements.size(); i++)
    {
        SAFE_DELETE(m_textElements[i]);
    }
}

unsigned int Line::GetTextElementsCount()
{
    return m_textElements.size();
}

/***
Explications : 

Ces deux suivantes instructions servent à faire en sorte qu'un élément de la ligne ne dépasse
la largeur de la console

1 -
2 -

***/
void Line::AddTextElement(std::wstring sTextElement, D3DXCOLOR colour, bool bBlinking)
{
    // Le début de la ligne : [Game]
    std::wstring s = CONSOLE->ToWString(MessageType::GetMessageType( GetType() ));

    unsigned int iStringLength = CONSOLE->GetStringLength( s + sTextElement ) + m_iLastElementLength;

    if (CONSOLE->TextIsOutOfWidth(s + sTextElement)) // || iStringLength > CONSOLE->GetWidth())
    {    
        // Après on segmente la ligne
        std::vector<std::wstring> moreLines;
        ParseLines(sTextElement, moreLines);

        for (unsigned int i = 0; i < moreLines.size(); i++)
        {
            std::wstring pCurrentString = moreLines[i];

            Line* pLine = new Line(m_type);

            pLine->AddTextElementSolo(pCurrentString, colour, bBlinking);

            CONSOLE->AddLine(pLine);

            m_iLastElementLength = 0;
        }    
    }
    else
    {
        AddTextElementSolo(sTextElement, colour, bBlinking);

        // On sauve la longueur en pixel de la dernière ligne affichée
        m_iLastElementLength = CONSOLE->GetStringLength(sTextElement);
    }
}

void Line::AddTextElementSolo(std::wstring sTextElement, D3DXCOLOR colour, bool bBlinking)
{
    TextElement* pTextElement = new TextElement(sTextElement, colour, bBlinking);
    m_textElements.push_back(pTextElement);

    if (bBlinking)
    {
        CONSOLE->AddBlinkingTextElement(pTextElement);
    }
}

// Analyse une ligne et la découpe en plusieurs lignes si le nombre
// de caractères de la ligne passé dépasse un certain seuil
void Line::ParseLines(std::wstring sText, std::vector<std::wstring>& moreLines)
{
    std::wstring s = CONSOLE->ToWString(MessageType::GetMessageType( GetType() ));

    const wchar_t* str = sText.c_str();
    std::wstring line = L"";

    for (unsigned int c = 0; c < sText.length(); c++)
    {    
        if (str1 == '\n' || CONSOLE->TextIsOutOfWidth(s + line))
        {
            moreLines.push_back(line);
            line = L"";
        }

        if (str1 != '\n')
            line += str1;
    }

    if (line.length() > 0)
    {
        moreLines.push_back(line);
    }
}

void Line::GetTextElements(std::vector<TextElement*>& elem)
{
    elem = m_textElements;
}

MessageType::Type Line::GetType()
{
    return m_type;
}

/****************************** CommandLinePrompt ******************************/

CommandLinePrompt::CommandLinePrompt() :
m_sPromptText(CONSOLE_PROMPT_TEXT),
m_pArrowSprite(nullptr),
m_iCursorPos(0)
{
    m_pArrowSprite = new Sprite2D(D3D10_RENDERER->GetDevice(), L"arrow.png", 10, 12);

    m_pArrowSprite->Initialize();
    m_pArrowSprite->SetColor(D3DXCOLOR(1.0f, 0.85f, 0.0f, 0.0f));
}

CommandLinePrompt::~CommandLinePrompt()
{
    delete m_pArrowSprite;
}

void CommandLinePrompt::Update(float fYPosition)
{
    float fPromptXOffset = CONSOLE->GetStringLength(L"_") + CONSOLE->GetStringLength(m_sPromptText);

    float fTotalWidth = GetCommandLengthPosition();

    SetArrowPosition(fPromptXOffset + fTotalWidth, fYPosition);
}

void CommandLinePrompt::AppendCommandLineText(const std::wstring sText)
{
    unsigned int iPromptCursorPos = GetCursorPos();

    std::wstring sCurrentCommandLine = GetCommandLineText();

    std::wstring sFirstPart = sCurrentCommandLine.substr(0, iPromptCursorPos);
    std::wstring sLastPart = sCurrentCommandLine.substr(iPromptCursorPos, sCurrentCommandLine.length());
    
    if (sLastPart.length() > 0)
    {
        sFirstPart += sText;
        m_sCommandLineText = sFirstPart + sLastPart;
        MoveCursorToRight();
    }
    else
    {
        m_sCommandLineText += sText;
        MoveCursorToRight();
    }
}
   
void CommandLinePrompt::SetText(const std::wstring sText)
{
    m_sCommandLineText = sText;
}

void CommandLinePrompt::Clear()
{
    m_sCommandLineText = L"";
    m_iCursorPos = 0;
}

bool CommandLinePrompt::HasText()
{
    return m_sCommandLineText.size() > 0;
}

const std::wstring CommandLinePrompt::GetPromptText()
{
    return m_sPromptText;
}

const std::wstring CommandLinePrompt::GetCommandLineText()
{
    return m_sCommandLineText;
}

void CommandLinePrompt::Render()
{
    m_pArrowSprite->Render();
}

void CommandLinePrompt::SetArrowTransparency(float fValue)
{
    m_pArrowSprite->SetTransparency(fValue);
}

void CommandLinePrompt::SetArrowPosition(float x, float y)
{
    m_pArrowSprite->SetPosition(x, y);
}

void CommandLinePrompt::SetArrowSize(unsigned int iArrowWidth, unsigned int iArrowHeight)
{
    m_pArrowSprite->SetImageSize(iArrowWidth, iArrowHeight);
}

unsigned int CommandLinePrompt::GetCursorPos()
{
    return m_iCursorPos;
}

void CommandLinePrompt::MoveCursorToLeft()
{
    if (m_iCursorPos > 0)
    {
        m_iCursorPos--;
    }
}

void CommandLinePrompt::MoveCursorToRight()
{
    float fPromptXOffset = CONSOLE->GetStringLength(m_sCommandLineText);

    if (GetCommandLengthPosition() < fPromptXOffset)
    {
        m_iCursorPos++;
    }
}

float CommandLinePrompt::GetCommandLengthPosition()
{
    float fTotalWidth = 0.0f;

    for (unsigned int i = 0; i < m_iCursorPos; i++)
    {
        std::wstring s = L"";
        s.push_back(m_sCommandLineText[i]);

        fTotalWidth += CONSOLE->GetStringLength(s);
    } 

    return fTotalWidth;
}

void CommandLinePrompt::ResetCursorPos()
{
    m_iCursorPos = m_sCommandLineText.length();
}

/****************************** CommandsHistory ******************************/

CommandsHistory::CommandsHistory() :
m_sLastCommand(L"")
{
}

CommandsHistory::~CommandsHistory()
{
    m_history.clear();
}

bool CommandsHistory::Empty()
{
    return m_history.size() == 0;
}

const std::wstring CommandsHistory::GetPrevious()
{
    if (!Empty())
    {
        if (m_sLastCommand == m_history.back())
        {
        }

        const std::wstring sCommandName = m_history.back();

        m_history.push_front(sCommandName);
        m_history.pop_back();

        m_sLastCommand = sCommandName;

        return sCommandName;
    }
    else
    {
        return EMPTY;
    }
}

const std::wstring CommandsHistory::GetNext()
{
    if (!Empty())
    {
        if (m_sLastCommand == m_history.front())
        {
        }

        const std::wstring sCommandName = m_history.front();

        m_history.push_back(sCommandName);
        m_history.pop_front();
        
        m_sLastCommand = sCommandName;

        return sCommandName;
    }
    else
    {
        return EMPTY;
    }
}

void CommandsHistory::Clear()
{
    m_history.clear();
}

void CommandsHistory::AddCommand(const std::wstring sCommandName)
{
    m_history.remove(sCommandName);
    m_history.push_back(sCommandName);

    if (m_history.size() > CONSOLE_MAX_COMMAND_HISTORY)
    {
        m_history.pop_front();
    }
}

/****************************** Console ******************************/

Console::Console() :
m_elemState(TransparencyState::FADE_IN),
m_iLinesStart(0),
m_iLinesOffset(0),
m_iMaxCharOnLine(0),
m_pFont(nullptr),
m_pConsoleTextSprite(nullptr),
m_pBackgroudImage(nullptr),
m_pBorderImage(nullptr),
m_aLegalChars(INPUT_LEGAL_CHARS),
m_fCharHeight(0),
m_fCharWidth(0),
m_fScrollOffset(1.0f),
m_pPrompt(nullptr)
{
}

Console::~Console()
{
    RemoveAllBlinkingTextElements();
}

void Console::Initialize()
{
    new CommandsManager();
    CreateTextFont();

    m_fConsoleHeight = (float) D3D10_RENDERER->GetViewportHeight() * CONSOLE_HEIGHT_FACTOR;
    m_fConsoleWidth = (float) D3D10_RENDERER->GetViewportWidth();

    /* Calcul des tailles d'un caractère */
    RECT rect = {0, 0, 0, 0};

    m_pFont->DrawText(nullptr, L"a", -1, &rect, DT_CALCRECT, D3DXCOLOR());

    m_fCharWidth = rect.right - rect.left;
    m_fCharHeight = rect.bottom - rect.top;

    m_iMaxLinesDisplayed = (unsigned int) (m_fConsoleHeight / (m_fCharHeight - CONSOLE_HEIGHT_PADDING / 2));
    m_iMaxCharOnLine = (unsigned int) ((m_fConsoleWidth)  / m_fCharWidth);

    // Le total des lignes affichables + la hauteur du prompt
    m_iTotalConsoleHeight = m_fConsoleHeight + m_fCharHeight + CONSOLE_HEIGHT_PADDING / 2;

    // Image de fond
    m_pBackgroudImage = new Sprite2D(D3D10_RENDERER->GetDevice(), L"background.jpg", m_fConsoleWidth, m_iTotalConsoleHeight);
    m_pBackgroudImage->Initialize();
    m_pBackgroudImage->SetPosition(0, -m_fConsoleHeight);

    // Bord du bas
    m_pBorderImage = new Sprite2D(D3D10_RENDERER->GetDevice(), L"border.png", m_fConsoleWidth, CONSOLE_BORDER_HEIGHT);
    m_pBorderImage->Initialize();

    // La ligne de commande et le prompt
    m_pPrompt = new CommandLinePrompt();
    
    // Ajout de lignes prédéfinies
    for (int i = 0; i < 0; i++)
    {
        Line* line = new Line(MessageType::MSG_GAME);
        //line->AddTextElement(L"En analyse, le nombre dérivé en un point d'une fonction à variable et valeurs réelles est le coefficient directeur de la tangente au graphe de cette fonction en ce point. C'est le coefficient directeur de l'approximation affine de cette fonction en ce point ; ce nombre n'est donc défini que si cette tangente — ou cette approximation — existe.", D3DXCOLOR(0.0f, 1.0f, 1.0f, 1.0f), false);

        line->AddTextElement(std::to_wstring(i), D3DXCOLOR(0.0f, 1.0f, 1.0f, 1.0f), false);

        AddLine(line);
    }

    LoadDefaultCommands();

    Print(L"En mathématiques, une métrique ou distance est une fonction qui définit la distance entre les éléments d'un ensemble. Un ensemble muni d'une distance est appelé un espace métrique. Toute distance induit une topologie sur un ensemble mais la réciproque est fausse : un espace topologique n'est pas toujours métrisable.");

    INPUT_MANAGER->SetKeyListener(this);

    m_pHistory = new CommandsHistory();

    // La console est caché dès le début
    SetState(HIDDEN);

    Print("Salut!");
}

void Console::CreateTextFont()
{
    ID3D10Device* pd3dDevice = D3D10_RENDERER->GetDevice();

    D3DX10CreateSprite(pd3dDevice, 0, &m_pConsoleTextSprite);

    D3DX10_FONT_DESC fd;
    fd.Height = CONSOLE_FONT_HEIGHT;
    fd.Width = CONSOLE_FONT_WIDTH;
    fd.Weight = 3;
    fd.MipLevels = 0;
    fd.Italic = false;
    fd.CharSet = OUT_DEFAULT_PRECIS;
    fd.Quality = ANTIALIASED_QUALITY;
    fd.PitchAndFamily = DEFAULT_PITCH;
    wcscpy(fd.FaceName, CONSOLE_FONT_NAME);

    D3DX10CreateFontIndirect(pd3dDevice, &fd, &m_pFont);
}

float Console::GetStringLength(std::wstring sText)
{
    RECT rect = {0, 0, 0, 0};

    m_pFont->DrawText(nullptr, sText.c_str(), -1, &rect, DT_CALCRECT, D3DXCOLOR());

    float fWidth = rect.right - rect.left;

    // On rajoute les espaces qui ne sont pas pris en compte
    if (!sText.empty() && sText.back() == L' ')
    {
        int c = sText.size() - 1;

        while (true)
        {
            if (sText.at(c) == L' ')
            {
                fWidth += 5;
            }
            else
            {
                break;
            }

            c--;

            // Au cas où...
            if (c < 0)
            {
                break;
            }
        }
    }

    return fWidth;
}

void Console::AddLine(Line* pLine)
{
    // On n'ajoute pas les lignes qui comportent uniquement
    // l'en-tête [Game] par exemple
    if (pLine->GetTextElementsCount() > 1)
    {
        m_lines.push_back(pLine);

        // On décale toutes les lignes
        if (m_lines.size() > m_iMaxLinesDisplayed)
        {
            m_iLinesStart++;
        }
    }
}

void Console::IncreaseScrollingPosition()
{
    if (m_iLinesStart > 0)
    {
        m_iLinesOffset++;
        m_iLinesStart--;
    }
}

void Console::DescreaseScrollingPosition()
{
    if (m_iLinesOffset > 0)
    {
        m_iLinesOffset--;
        m_iLinesStart++;
    }
}

void Console::SetState(ConsoleState state)
{
    m_state = state;
}

void Console::ToggleState()
{
    if (m_state == SHOWN)
    {
        m_state = MOVING_UP;
    }
    else if (m_state == HIDDEN)
    {
        m_state = MOVING_DOWN;
    }
}

void Console::Print(Line* pLine)
{
    AddLine(pLine);
}

void Console::Print(const std::wstring& sMessage, D3DXCOLOR colour, MessageType::Type type)
{
    Line* pLine = new Line(type);

    pLine->AddTextElement(sMessage, colour, false);

    AddLine(pLine);
}

void Console::Print(const std::wstring& sMessage, MessageType::Type type)
{
    Line* pLine = new Line(type);

    pLine->AddTextElement(sMessage, D3DXCOLOR(1.0f, 1.0f,1.0f, 1.0f), false);

    AddLine(pLine);
}

void Console::Print(const std::wstring& sMessage, D3DXCOLOR colour)
{
    Line* pLine = new Line(MessageType::MSG_GAME);

    pLine->AddTextElement(sMessage, colour, false);

    AddLine(pLine);
}

void Console::Print(const std::wstring& sMessage)
{
    Line* pLine = new Line(MessageType::MSG_GAME);

    pLine->AddTextElement(sMessage, D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f), false);

    AddLine(pLine);
}

void Console::Print(const std::string& sMessage)
{
    Line* pLine = new Line(MessageType::MSG_GAME);

    pLine->AddTextElement(ToWString(sMessage), D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f), false);

    AddLine(pLine);
}

void Console::AddBlankLine()
{
    Line* pLine = new Line(MessageType::MSG_BLANK);

    pLine->AddTextElement(L"", D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f), false);

    AddLine(pLine);
}

bool Console::IsVisible()
{
    return m_state != HIDDEN;
}

void Console::AddToHistory(const std::wstring& cmd)
{
    m_pHistory->AddCommand(cmd);
}

bool Console::IsCharAuthorized(const wchar_t key)
{
    for (unsigned int c = 0; c < m_aLegalChars.size(); c++)
    {
        if (m_aLegalChars1 == key)
        {
            return true;
        }
    }

    return false;
}

void Console::AddBlinkingTextElement(TextElement* elem)
{
    m_blinkingTextElements.push_back(elem);
}

void Console::RemoveAllBlinkingTextElements()
{
    m_blinkingTextElements.clear();
}

void Console::Clear()
{
    for (unsigned int i = 0; i < m_lines.size(); i++)
    {
        delete m_lines[i];
    }

    m_lines.clear();

    RemoveAllBlinkingTextElements();

    m_iLinesOffset = 0;
    m_iLinesStart = 0;
}

void Console::LoadDefaultCommands()
{
    CONSOLE_COMMANDS_MANAGER->AddCommand( new Command_QUIT() );
    CONSOLE_COMMANDS_MANAGER->AddCommand( new Command_HELP() );
    CONSOLE_COMMANDS_MANAGER->AddCommand( new Command_EXIT() );
    CONSOLE_COMMANDS_MANAGER->AddCommand( new Command_VERSION() );
    CONSOLE_COMMANDS_MANAGER->AddCommand( new Command_CLS() );
}

void Console::TabComplete()
{    
    std::wstring sCurrentCommandLine = m_pPrompt->GetCommandLineText();

    std::vector<std::string> cmds;
    CONSOLE_COMMANDS_MANAGER->GetCommandsNames(cmds);
    
    // On stocke toutes les commandes qui commence par le même début que la saisie
    // et ceci lorsque la liste est vide
    if (sCurrentCommandLine.length() > 0 && m_tabHistory.empty())
    {
        for (int i = 0; i < cmds.size(); i++)
        {
            std::wstring cmd = ToWString(cmds[i]);

            if (cmd.size() > sCurrentCommandLine.size())
            {
                if (cmd.substr(0, sCurrentCommandLine.size()) == sCurrentCommandLine)
                {
                    m_tabHistory.push_back(cmd);
                }
            }
        }        
    }
    // Sinon on stocke toutes les commandes existantes
    else
    {
        for (int i = 0; i < cmds.size(); i++)
        {
            std::wstring cmd = ToWString(cmds[i]);

            m_tabHistory.push_back(cmd);
        }
    }

    // On échange les commandes de manière circulaire dans la liste
    if (!m_tabHistory.empty())
    {
        std::wstring command = m_tabHistory.front();

        m_tabHistory.pop_front();
        m_tabHistory.push_back(command);

        m_pPrompt->SetText(command);
    }
}

void Console::SoloKeyPressed(const KeyEvent& arg)
{
    if (arg.key == DIK_F1)
    {
        ToggleState();
    }

    if (!IsVisible())
    {
        return;
    }

    if (IsCharAuthorized(arg.text))
    {
        std::wstring wStr;
        wStr.push_back( arg.text );

        m_pPrompt->AppendCommandLineText(wStr);

        /* On reset l'historique de selection automatique
        de commande par tabulation */
        m_tabHistory.clear();
    }

    if (arg.key == DIK_RETURN)
    {
        if (!m_pPrompt->HasText())
        {
            return;
        }

        ResetScrollingPosition();

        DataParameters param;
        const std::wstring sCommandLineCaption = m_pPrompt->GetCommandLineText();

        GetCommandLineArguments(sCommandLineCaption, param);

        if (param.getParametersCount() > 0)
        {
            std::string sCommandName = param.getParameterAsString("0");

            // On appel la commande spécifiée
            CONSOLE_COMMANDS_MANAGER->InvokeCommand(sCommandName, param);

            AddToHistory( sCommandLineCaption );
        }
            
        m_pPrompt->Clear();
    }
    else if (arg.key == DIK_LEFTARROW)
    {
        m_pPrompt->MoveCursorToLeft();
    }
    else if (arg.key == DIK_RIGHTARROW)
    {
        m_pPrompt->MoveCursorToRight();
    }
    else if (arg.key == DIK_BACK)
    {
        DeletePreviousCharacter();
    }
    else if (arg.key == DIK_DELETE)
    {
        DeleteNextCharacter();
    }
    else if (arg.key == DIK_TAB)
    {
        TabComplete();
        m_pPrompt->ResetCursorPos();
    }
    else if(arg.key == DIK_UP)
    {
        const std::wstring sCommand = m_pHistory->GetPrevious();

        if (sCommand != EMPTY)
        {
            m_pPrompt->SetText(sCommand);
            m_pPrompt->ResetCursorPos();
        }
    }
    else if(arg.key == DIK_DOWN)
    {
        const std::wstring sCommand = m_pHistory->GetNext();

        if (sCommand != EMPTY)
        {
            m_pPrompt->SetText(sCommand);
            m_pPrompt->ResetCursorPos();
        }
    }
}

void Console::SoloKeyReleased(const KeyEvent& arg)
{
}    

void Console::RepetitiveKeyPressed(const KeyEvent& arg)
{
    if (!IsVisible())
    {
        return;
    }

    if (arg.key == (DIK_PGUP))
    {
        IncreaseScrollingPosition();
    }

    if (arg.key == (DIK_PGDN))
    {
        DescreaseScrollingPosition();
    }
}

void Console::RepetitiveKeyReleased(const KeyEvent& arg)
{
}    

void Console::GetCommandLineArguments(const std::wstring& sCommandLine, DataParameters& param)
{
    std::string sArgument = "";
    unsigned int iArgumentCount = 0;

    for (unsigned int c = 0; c < sCommandLine.length(); c++)
    {
        if (sCommandLine1 == ' ')
        {
            if (sArgument.length() > 0)
            {
                param.setParam(iArgumentCount, sArgument);

                iArgumentCount++;
                sArgument = "";
            }
        }
        else
        {
            sArgument += sCommandLine1;
        }
    }

    if (sArgument.length() > 0)
    {
        param.setParam(iArgumentCount, sArgument);
    }
}

void Console::Render(float fTimeElapsedSinceLastFrame)
{    
    if (!IsVisible())
    {
        return;
    }

    UpdateSprites(fTimeElapsedSinceLastFrame);

    // On commence par afficher le texte à partir d'une certaine marge
    float iLastTextElementXPos = CONSOLE_LINE_BEGINNING_WIDTH;
    int iLineID = 0;
    float fLastLineYPos = 0;
    float fScroll = -(m_fScrollOffset * m_fConsoleHeight);
    float fHeightPadding = m_fCharHeight / 2 + CONSOLE_HEIGHT_PADDING;

    m_pConsoleTextSprite->Begin(D3DX10_SPRITE_SAVE_STATE);
    for (unsigned int i = m_iLinesStart; i < m_lines.size() - m_iLinesOffset; i++)
    {
        Line* pLine = m_lines[i];

        std::vector<TextElement*> elems;
        pLine->GetTextElements(elems);

        for (unsigned int j = 0; j < elems.size(); j++)
        {
            TextElement* pElement = elems[j];

            float fLastLineYPos = (float) (iLineID) * fHeightPadding;

            RECT rectangle = {iLastTextElementXPos, fScroll + fLastLineYPos, 0, 0};

            D3DXCOLOR color = pElement->GetColour();

            std::wstring sText = pElement->GetText();

            m_pFont->DrawText(m_pConsoleTextSprite, sText.c_str(), -1, &rectangle, DT_NOCLIP, color);            
            
            iLastTextElementXPos += GetStringLength( pElement->GetText() ) ;
        }

        iLastTextElementXPos = CONSOLE_LINE_BEGINNING_WIDTH;
        iLineID++;
    }

    /******** Command Line Prompt ********/

    const std::wstring sPromptText = m_pPrompt->GetPromptText();
    const std::wstring sCommandLineText = m_pPrompt->GetCommandLineText();

    float y = fScroll + (float) (m_iMaxLinesDisplayed) * fHeightPadding;

    RECT promptRectangle = {CONSOLE_LINE_BEGINNING_WIDTH, y, 0, 0};
    RECT commandLineRectangle = {sPromptText.length() * m_fCharWidth, y, 0, 0};

    D3DXCOLOR promptColor(1.0f, 0.85f, 0.0f, 1.0f);
    D3DXCOLOR commandLineColor(0.0f, 0.3f, 1.0f, 1.0f);

    m_pFont->DrawText(m_pConsoleTextSprite, sPromptText.c_str(), -1, &promptRectangle, DT_NOCLIP, promptColor);        
    m_pFont->DrawText(m_pConsoleTextSprite, sCommandLineText.c_str(), -1, &commandLineRectangle, DT_NOCLIP, commandLineColor);

    float fBorderScroll = y + m_fCharHeight + CONSOLE_HEIGHT_PADDING / 2;
    m_pBorderImage->SetPosition(0, fBorderScroll);

    float fY = y + (m_fCharHeight / 2 - CONSOLE_HEIGHT_PADDING / 2);
    m_pPrompt->Update(fY);

    m_pConsoleTextSprite->End();

    // On affiche le prompt après tous les précédents rendus
    m_pPrompt->Render();
}

void Console::UpdateSprites(float fTimeElapsedSinceLastFrame)
{
    if (!IsVisible())
    {
        return;
    }

    m_pBackgroudImage->Render();
    m_pBorderImage->Render();

    // Montrer
    if (m_state == MOVING_DOWN)
    {      
        m_fScrollOffset -= fTimeElapsedSinceLastFrame * CONSOLE_MOVE_SPEED;

        if (m_fScrollOffset <= 0.0)
        {
            m_fScrollOffset = 0.0;

              SetState(SHOWN);
        } 

        m_pBackgroudImage->SetPosition(0, -m_fScrollOffset * m_fConsoleHeight);
    }
 
    // Cacher
    if (m_state == MOVING_UP)
    {
        m_fScrollOffset += fTimeElapsedSinceLastFrame * CONSOLE_MOVE_SPEED;

        if (m_fScrollOffset >= 1.0)
        {
            m_fScrollOffset = 1.0;

              SetState(HIDDEN);
        }

        m_pBackgroudImage->SetPosition(0, -m_fScrollOffset * m_fConsoleHeight);
    }

    // Mise à jour des éléments qui alterne en couleur alpha
    if (m_state == SHOWN)
    {
        static float alpha = 0.0f;

        if (m_elemState == TransparencyState::FADE_IN)
        {
            alpha -= fTimeElapsedSinceLastFrame;

            if (alpha < 0.0f)
            {
                m_elemState = TransparencyState::FADE_OUT;
            }
        }
        else if (m_elemState == TransparencyState::FADE_OUT)
        {
            alpha += fTimeElapsedSinceLastFrame;
    
            if (alpha > 1.0f)
            {
                m_elemState = TransparencyState::FADE_IN;
            }
        }

        for (unsigned int i = 0; i < m_blinkingTextElements.size(); i++)
        {        
            TextElement* pTextElem = m_blinkingTextElements[i];
            static D3DXCOLOR color = pTextElem->GetColour();

            pTextElem->SetColour(D3DXCOLOR(color.r, color.g, color.b, alpha));
        }

        m_pPrompt->SetArrowTransparency(alpha);
    }
}

/* todo : utils.cpp */
std::string Console::ToString(const std::wstring& sText)
{
    std::string text = "";

    text.assign(sText.begin(), sText.end());

    return text;
}

std::wstring Console::ToWString(const std::string& sText)
{
    std::wstring text = L"";

    text.assign(sText.begin(), sText.end());

    return text;
}

unsigned int Console::GetWidth()
{
    return m_fConsoleWidth;
}

bool Console::TextIsOutOfWidth(const std::wstring sText)
{
    // On applique un certain espace entre les bords de la console
    unsigned int iTotalWidth = GetStringLength(sText) + CONSOLE_LINE_BEGINNING_WIDTH * 4;

    return iTotalWidth >= GetWidth();
}

void Console::ResetScrollingPosition()
{
    if (m_lines.size() > m_iMaxLinesDisplayed)
    {
        for (int i = 0; i < m_lines.size(); i++)
        {
            if (m_iLinesOffset > 0)
            {
                m_iLinesOffset--;
                m_iLinesStart++;
            }
        }    
    }
}

unsigned int Console::GetCharactWidth()
{
    return m_fCharWidth;
}

void Console::DeleteNextCharacter()
{
    unsigned int iPromptCursorPos = m_pPrompt->GetCursorPos();

    std::wstring sCurrentCommandLine = m_pPrompt->GetCommandLineText();

    std::wstring sFirstPart = sCurrentCommandLine.substr(0, iPromptCursorPos);
    std::wstring sLastPart = sCurrentCommandLine.substr(iPromptCursorPos, sCurrentCommandLine.length());

    if (sFirstPart.length() >= 0)
    {
        if (sLastPart.length() > 0 )
        {    
            sLastPart.erase(0, 1);
            m_pPrompt->SetText(sFirstPart + sLastPart);    
        }
    }
}

void Console::DeletePreviousCharacter()
{        
    unsigned int iPromptCursorPos = m_pPrompt->GetCursorPos();

    std::wstring sCurrentCommandLine = m_pPrompt->GetCommandLineText();

    std::wstring sFirstPart = sCurrentCommandLine.substr(0, iPromptCursorPos);
    std::wstring sLastPart = sCurrentCommandLine.substr(iPromptCursorPos, sCurrentCommandLine.length());

    if (sFirstPart.length() > 0)
    {
        if (sLastPart.length() > 0 )
        {    
            sFirstPart.pop_back();
            m_pPrompt->SetText(sFirstPart + sLastPart);    
            m_pPrompt->MoveCursorToLeft();
        }
        else if (sCurrentCommandLine.length() > 0)
        {
            sCurrentCommandLine.pop_back();
            m_pPrompt->SetText(sCurrentCommandLine);
            m_pPrompt->MoveCursorToLeft();
        }
    }
}

Résumé :

Voici le code de cet article : archive.

Les principales étapes de rendu de DirectX 10

Intro :

Le rendu graphique de DirectX 10 consiste en plusieurs étapes.

Prérequis :

Savoir utiliser et implémenter DirectX 10.

Explications :

Voici un schéma qui les résume :

220px-D3D_Pipeline.svg

Les rectangles carrés indiquent que l’étape de rendu ne peut pas être configurée par un script ou programme HLSL.

A l’inverse, les rectangles arrondies indiquent que l’étape de rendu peut être configurée par un script ou programme HLSL.

Voir cet article si vous ne savez ce qu’est le HLSL.

 


 

Étape Input Assembler :

Cette étape prépare les données géométriques à la chaîne de rendu 3D. Les données des Vertex Buffers et des Indexes Buffers sont agencées d’une certaine façon dans la mémoire de la carte graphique et à partir de ceux-ci, l’étape Input Assembler créée les primitives géométriques correspondantes (triangles, lignes, points, etc…) afin de les passer au Vertex Shader.

Étape Vertex Shader :

Cette étape permet de modifier les vertices en entrée. Elle consiste à transformer ces vertices depuis les coordonnées Object-space vers les coordonnées Clip-Space ; c’est-à-dire les coordonnées du modèle d’origine vers les coordonnes du cadre de vue de l’écran 2D.
Elle sert aussi à changer la position des vertex dans le repère 3D. Elle est responsable aussi d’effectuer les animations des modèles / personnages du jeu et permet aussi d’effectuer un rendu basique de lumière (en l’occurence du per-pixel ligthing).

Voir le Lexique si vous ne comprenez pas tous ces termes.

Elle traite un vertex à la fois : c’est-à-dire que cette étape est exécutée en boucle pour chaque vertex.

Les données en sortie de cette étape vers le Vertex shader (qui est en fait une fonction toute simple avec entrée et sortie) peuvent être : les coordonnées des textures, la couleur des vertex, etc…

Étape Geometry Shader :

Cette étape optionnelle permet de modifier complètement la géométrie des modèles / meshes.
Au lieu de recevoir vertex par vertex cette étape reçoit une primitive totalement assemblée (une ligne ou un triangle par exemple) ; de là on peut générer d’autres primitives à la volée. Le Geometry Shader peut ignorer une primitive ou en générer de nouvelles.

Étape Stream-Output Stage :

Génère le flux des primitives géométriques depuis la mémoire vers l’étape Rasterizer.

Étape Rasterizer :

Cette étape permet de rassembler les primitives afin de les envoyer au Pixel Shader. Elle s’occupe aussi de couper les primitives qui dépassent le cadre de vue de l’écran.

Étape Pixel Shader :

Cette étape s’occupe de modifier l’affichage de chaque pixel et pour chacun de ceux-ci elle renvoie une couleur en sortie.

Étape Output Merger :

Cette étape s’occupe quel pixel doit être affichée à l’écran. Les pixels générés sont calculés sur la base du Pixel Shader, du contenu des Render Targerts, du contenu du Depth Buffer et du contenu du Stencil Buffer.

 

Résumé :

Nous avons énuméré toutes les étapes du rendu 3D de DirectX 10.

Références :

– Pipeline Stages (Direct3D 10) – DirectX10 June 2010 SDK Documentation

Introduction à DirectX 10 – Fenêtrage – partie 1

Intro :

Ceci constitue une introduction à la version 10 de DirectX.

DirectX est une interface entre le programmeur et la carte graphique.

Dans cette partie je vais vous apprendre à l’initialiser de manière rudimentaire.

Prérequis :

Savoir programmer en C++.

Première partie :

Création de la fenêtre de rendu.

Explications :

Pensez à configurer les chemins d’accès du SDK de DirectX  dans VC++ ! Si vous ne savez pas comment faire : lire ce tutoriel.

On a besoin d’utiliser une fonction qui boucle à chaque fois que l’application reçoit
un message :

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
 
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
 
    return 0;
}


Ensuite créez un fichier System.cpp et un fichier System.h où vous mettrez les fonctions que je vous énoncerai.

Voici le fichier System.h :


#ifndef SYSTEM_H
#define SYSTEM_H

#include "Defines.h"
#include "D3D10Renderer.h"

class System

{
public:
    System();
    virtual ~System();

    bool Initialize();
    void Shutdown();
    bool Frame();
    bool Run();

    HRESULT SetupTheWindow();
    void DestroyTheWindow();

private:
    // Objet qui gère le rendu de DirectX
    D3D10Renderer* m_pRenderer;
    LPCWSTR m_sApplicationName;
    HINSTANCE m_hInstance;
    HWND m_Hwnd;
    bool m_bFullScreen;
};

#endif

Dans le fichier System.cpp. On initialise d’abord une fenêtre Windows ; voici la fonction pour l’initialiser :


HRESULT System::SetupTheWindow()
{
    // Détermine la résolution de l'écran.
    unsigned int iScreenWidth  = GetSystemMetrics(SM_CXSCREEN);
    unsigned int iScreenHeight = GetSystemMetrics(SM_CYSCREEN);

    // Structure de la classe à configurer
    WNDCLASSEX wcex;

    // Taille de la structure WNDCLASSEX
    wcex.cbSize = sizeof( WNDCLASSEX );

    // Style de la classe
    wcex.style = CS_HREDRAW | CS_VREDRAW;

    // Pointeur de la fonction de boucle de fenêtre (Callback)
    wcex.lpfnWndProc = WndProc;

    // Inutile à comprendre
    wcex.cbClsExtra = 0;

    // Inutile à comprendre
    wcex.cbWndExtra = 0;

    // Obtention de l'instance de cette application
    wcex.hInstance = m_hInstance;

    // Icone de la fenêtre
    wcex.hIcon = LoadIcon(NULL, IDI_WINLOGO);

    // Icone du pointeur de la souris
    wcex.hCursor = LoadCursor( NULL, IDC_ARROW );

    // Inutile à comprendre
    wcex.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );

    // Inutile à comprendre
    wcex.lpszMenuName = NULL;

    // Nom de la classe enregistrée
    wcex.lpszClassName = m_sApplicationName;

    wcex.hIconSm = wcex.hIcon;

    DEVMODE dmScreenSettings;

    if(m_bFullScreen)
    {
        ZeroMemory(&dmScreenSettings, sizeof(dmScreenSettings));

        dmScreenSettings.dmSize       = sizeof (dmScreenSettings);
        dmScreenSettings.dmPelsWidth  = (unsigned long) iScreenWidth;
        dmScreenSettings.dmPelsHeight = (unsigned long) iScreenHeight;
        dmScreenSettings.dmBitsPerPel = 32;            
        dmScreenSettings.dmFields     = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

        ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);
    }

    if (!RegisterClassEx(&wcex))
        return E_FAIL;

    // Créé la fenêtre
    RECT rc = { 0, 0, 640, 480 };
    AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );
    m_Hwnd = CreateWindow(m_sApplicationName, m_sApplicationName, WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, m_hInstance,
                          NULL);
    if (!m_Hwnd)
        return E_FAIL;

    ShowWindow(m_Hwnd, SW_SHOW);

    SetForegroundWindow(m_Hwnd);
    SetFocus(m_Hwnd);

    return S_OK;
}

 

Toujours dans System.cpp, la fonction pour détruire la fenêtre :


void System::DestroyTheWindow()
{
    // Change les paramètres d'affichage si l'on quitte le mode plein écran
    if(m_bFullScreen)
    {
        ChangeDisplaySettings(NULL, 0);
    }

    // Détruit la fenêtre.
    DestroyWindow(m_Hwnd);
    m_Hwnd = NULL;

    // Détruit l'instance de l'application
    UnregisterClass(m_sApplicationName, m_hInstance);
    m_hInstance = NULL;
}

 

La fonction pour initialiser la fenêtre et le Renderer de DirectX 10 :


bool System::Initialize()
{
    SetupTheWindow();

    m_pRenderer = new D3D10Renderer(m_Hwnd);
    bool bSucess = m_pRenderer->Initialize(m_bFullScreen);

    if (!bSucess)
    {
        MessageBoxA(NULL, "Erreur d'initialisation de DirectX !", "Erreur", MB_ICONHAND | MB_OK);
        return false;
    }

    return true;
}

 

 


 

La fonction WinMain est dans le fichier Main.cpp et est implémentée de cette façon :

#include "D3D10Renderer.h"
#include "System.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
    System* pSystem = new System();

    bool bSucess = pSystem->Initialize();

    if (bSucess)
    {
        pSystem->Run();
    }

    pSystem->Shutdown();

    SAFE_DELETE(pSystem);

    return 0;
}

 


 

Ensuite créez un fichier D3D10Renderer.cpp et un fichier D3D10Renderer.h où vous mettrez les fonctions que je vous énoncerai.


bool D3D10Renderer::Initialize(bool bFullScreen)
{
    HRESULT hr = S_OK;;
    RECT rc;

    GetClientRect(m_hWnd, &rc);
    UINT width = rc.right - rc.left;
    UINT height = rc.bottom - rc.top;

    UINT createDeviceFlags = 0;

#ifdef _DEBUG
    createDeviceFlags |= D3D10_CREATE_DEVICE_DEBUG;
#endif
    // Une swap chain représente en fait le back buffer
    // et le front buffer (principe nommé double buffering)
    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.BufferCount = 1;
    sd.BufferDesc.Width = width;
    sd.BufferDesc.Height = height;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = m_hWnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = !bFullScreen;
    sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    hr = D3D10CreateDeviceAndSwapChain(NULL, D3D10_DRIVER_TYPE_HARDWARE, NULL, createDeviceFlags,
                                        D3D10_SDK_VERSION, &sd, &m_pSwapChain, &m_pd3dDevice);
    if (FAILED(hr))
        return false;

    // Créé le back buffer
    ID3D10Texture2D* pBackBuffer;
    hr = m_pSwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&pBackBuffer);
    if (FAILED(hr))
        return false;
    // Créé la render target
    hr = m_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &m_pRenderTargetView);
    pBackBuffer->Release();
    if (FAILED(hr))
        return false;

    m_pd3dDevice->OMSetRenderTargets( 1, &m_pRenderTargetView, NULL );

    // Création du viewport
    D3D10_VIEWPORT vp;
    vp.Width = width;
    vp.Height = height;
    vp.MinDepth = 0.0f;
    vp.MaxDepth = 1.0f;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;
    m_pd3dDevice->RSSetViewports( 1, &vp );

    
    return true;
}

 

La fonction pour afficher le rendu :


void D3D10Renderer::Render()
{
    float afClearColor[4] = {0.0f, 0.125f, 0.3f, 1.0f}; 

    m_pd3dDevice->ClearRenderTargetView(m_pRenderTargetView, afClearColor);
    m_pSwapChain->Present(1, 0);
}

 

Résumé :

Nous avons affiché une fenêtre prête à recevoir le rendu de DirectX.

fenetre

Voici l’archive du code complet pour cette partie : DirectX 10 Tutoriel – Partie 1.zip

Voilà le code et les explications pour initialiser la fenêtre de DirectX 10.
Il reste beaucoup de choses à intégrer pour faire un jeu mais vous saurez au moins comment intégrer DirectX 10.

Références :

– https://takinginitiative.wordpress.com/directx10-tutorials/

– http://www.rastertek.com/tutdx10.html

– DirectX SDK June 2010 Documentation Samples

Classe CommandLineManager pour récupérer les paramètres de votre exécutable

Terminal-icon-shell-linux-unix

Intro :

[utiliser la classe DataParameters]

On peut passer à exécutable des paramètres comme suit : mon_exécutable.exe -param1 arg1 arg2 -param2 arg3

Cette classe va vous permettre de les récupérer vos arguments plus facilement.

Préréquis :

– Savoir lire du C++

– Savoir écrire un programme rudimentaire  en C++

Explications :

Voici le fichier d’en-tête ParametersManager.h :

#ifndef PARAMETERS_MANAGER_H
#define PARAMETERS_MANAGER_H

/**
* \brief Une classe pour gérer les paramètres des arguments d'entrée de
* ligne de commande du programme.
*/
class Parameter
{
public:
    Parameter(std::string sName);
    virtual ~Parameter();

    // Met un argument du paramètre
    void pushArgument(const std::string& sArgument);
    // Récupère le nom du paramètre
    const std::string& getName() const;
    // Récupère un argument
    const char* getArgument(unsigned int iArgumentNumber) const;
    // Retourne le nombre d'argument
    int getArgumentCount() const;

private:
    std::string m_sName;
    std::vector<std::string> m_vArguments;
};

class CommandLineManager : public Singleton<CommandLineManager>
{
public:
    CommandLineManager(int argc, char* argv[]);
    ~CommandLineManager();
    
    // Est-ce que la ligne de commande a ce paramètre
    bool hasParameter(const std::string& sParameter);
    // Le nombre de paramètre
    int getParameterCount();
    // Récupère le paramètre
    Parameter* getParameter(const std::string& sParameter);

private:
    // Evalue la ligne de commande
    void parseCommandLineArguments(int iArgumentCount, char* paArguments[]);

private:
    std::map<std::string, Parameter*> m_vParameters;
};

#endif

Voici le fichier .cpp :

// Format : -PARAM ARG1 ARG2...
Parameter::Parameter(std::string sName) :
m_sName(sName)
{
}

Parameter::~Parameter()
{
}

// On ajoute les arg1, arg2 à la suite des un et des autres
void Parameter::pushArgument(std::string sArgument)
{
    m_vArguments.push_back(sArgument);
}

const char* Parameter::getArgument(unsigned int iArgumentNumber)  const
{
    if (iArgumentNumber < m_vArguments.size())
        return m_vArguments[iArgumentNumber].c_str();
    else
        return "Unknown";
}

int Parameter::getArgumentCount() const
{
    return m_vArguments.size();
}

/********************************** Déclaration de la classe CommandLineManager **********************************/

CommandLineManager::CommandLineManager(int argc, char* argv[])
{
    parseCommandLineArguments(argc, argv);
}

CommandLineManager::~CommandLineManager()
{
    // Libère les objets paramètre de la mémoire.
    std::map<std::string, Parameter*>::iterator it;
    for (it = m_vParameters.begin(); it != m_vParameters.end(); ++it)
    {
        Parameter* pParam = (*it).second;
        SAFE_DELETE(pParam);
    }
}

void CommandLineManager::parseCommandLineArguments(int iArgumentCount, char* paArguments[])
{
    Parameter* pCurrentParameter = nullptr;

    // Exemple de ligne de commande : game.exe -fullscreen -res 800 600 -opengl
    for (int i = 0; i < iArgumentCount; ++i)
    {
        std::string sCurrentArgumentName(paArguments[i]);    

        // Si on a un nouvel argument qui commence par un '-'.
        if (sCurrentArgumentName[0] == '-')
        {
            // On enlève l'indicateur de paramètre (premier caractère).
            sCurrentArgumentName.erase(0, 1);
            // On créé un nouveau objet paramètre avec son nom.
            pCurrentParameter = new Parameter(sCurrentArgumentName);
            // On l'ajoute dans la liste des arguments.
            m_vParameters[sCurrentArgumentName] = pCurrentParameter;
        }
        else if (pCurrentParameter)// C'est peut-être un argument, (paramètre qui ne commence pas par '-').
        {
            pCurrentParameter->pushArgument(sCurrentArgumentName);
        }
    }
}

bool CommandLineManager::hasParameter(const std::string& sParameter)
{
    return m_vParameters.count(sParameter) > 0;
}

int CommandLineManager::getParameterCount()
{
    return m_vParameters.size();
}

Parameter* CommandLineManager::getParameter(const std::string& sParameter)
{
    if (m_vParameters.count(sParameter) > 0)
    {
        return m_vParameters[sParameter];
    }
    else
    {
        return nullptr;
    }
}

 

Utilisation :

On utilise ce bout de code dans la fonction main (int argc, char** argv) :

CommandLineManager* pCommandLineManager = new CommandLineManager(argc, argv);

// Bloc d'analyse des arguments de ligne de commande, etc...
unsigned int iWindowWidth = SYSTEM_MINIMUM_WINDOW_WIDTH;
unsigned int iWindowHeight = SYSTEM_MINIMUM_WINDOW_HEIGHT;
bool bFullScreen = false;

// Si on a au moins un paramètre.
if (pCommandLineManager->getParameterCount() > 0)
{
    // Paramètre de résolution de la fenêtre
    if (pCommandLineManager->hasParameter("res"))
    {
        Parameter* pWindowResParameter = pCommandLineManager->getParameter("res");

        if (pWindowResParameter->getArgumentCount() > 1)
        {
            // Acquisition des paramètres de résolution de la fenêtre
            std::string sResolutionWidth = pWindowResParameter->getArgument(0);
            std::string sResolutionHeight = pWindowResParameter->getArgument(1);

            iWindowWidth = atoi(sResolutionWidth.c_str());
            iWindowHeight = atoi(sResolutionHeight.c_str());
        }
    }

    if (pCommandLineManager->hasParameter("fullscreen"))
        bFullScreen = true;
}

 

Résumé :

Nous avons présenté une méthode pour analyser vos arguments en ligne de commande.

Utilisation du préprocesseur

224706

Intro :

En C++, avant de compiler le programme, il est possible d’effectuer certaines modifications sur le code source. Le programme effectuant ces modifications s’appelle le préprocesseur. Les commandes destinées au préprocesseur commencent toutes par # en début de ligne.

Prérequis :

Savoir les rudiments du C++.

Inclusion de fichiers sources :

On peut inclure des fichiers .h avec la directive #include :


#include "EventManager.h"
#include <iostream>

Cela a pour effet d’inclure le code source du fichier à l’emplacement indiqué.

Directives du préprocesseur :

#define : permet de définir une constante globale :


#define CAMERA_MOVE_SPEED 2.0f

#ifndef : permet de tester si la variable testée n’est pas définie

#ifdef : permet de tester si la variable testée est définie

Macro prédéfinies :

__DATE__ : date de compilation du fichier source actuel

__FILE__  : nom du fichier source actuel

__LINE__ : numéro de ligne dans le fichier source actuel

__TIME__ : heure de compilation la plus récente du fichier source actuel

__FUNCTION__ : nom de la fonction en cours

_WIN32 : défini si le programme est compilé sous Windows ou non

__VA_ARGS__ : voir cette article

Pragma :

Pour supprimer des warnings que vous jugez inutiles :


#pragma warning (disable : 4018 )

Pour spécifier au compilateur que le fichier ne doit être inclu qu’une fois :


#pragma once

Pour spécifier au compilateur de lier une librairie donnée :

#pragma comment(lib, "Newton.lib")

 

Résumé :

Nous avons énuméré les principales fonctionnalités du préprocesseur en C++.

Classe DataParameters pour faciliter l’échange d’information entre modules

todo : refaire le stream reset avec une méthode

Intro :

Parfois il est utile de regrouper des paramètres ensemble afin de les échanger entre modules.

Explications :

Voici le fichier DataParameters.h :

#ifndef DATA_PARAMETERS_H
#define DATA_PARAMETERS_H
 
#include <string>
#include <map>

class DataParameters
{
public:
    // Différents types de variables à stocker
    struct Param { int i; float f; std::string s; };
 
    DataParameters();
    virtual ~DataParameters();
     
    // Premier numéro d'index (c'est-à-dire le numéro 0)
    DataParameters(int param);
    DataParameters(float param);
    DataParameters(char* param);
    DataParameters(const std::string& sParam);
 
    // Par nom
    DataParameters(const std::string& sParamName, int param);
    DataParameters(const std::string& sParamName, float param);
    DataParameters(const std::string& sParamName, const char* param);
    DataParameters(const std::string& sParamName, const std::string& sParam);
 
    /******************/
 
    // Par nom
    void setParam(const std::string& sParamName, int param);
    void setParam(const std::string& sParamName, float param);
    void setParam(const std::string& sParamName, const char* param);
    void setParam(const std::string& sParamName, const std::string& sParam);
 
    // Par index
    void pushParam(int param);
    void pushParam(float param);
    void pushParam(const char* param);
    void pushParam(const std::string& param);
 
    Param* getParameter(const std::string& sParamName);
    int getParameterAsInt(const std::string& sParamName);
    float getParameterAsFloat(const std::string& sParamName);
    const char* getParameterAsString(const std::string& sParamName);
 
    // Par accès au premier argument
    Param* getFirstParameter();
    int getFirstParameterAsInt();
    float getFirstParameterAsFloat();
    const char* getFirstParameterAsString();

    unsigned int getParamNumber();
 
    // Opérateurs >> flux pour lire les données
    DataParameters& operator >>(bool& data);
    DataParameters& operator >>(int& data);
    DataParameters& operator >>(unsigned int& data);
    DataParameters& operator >>(float& data);
    DataParameters& operator >>(char* data);
    DataParameters& operator >>(std::string& data);

    // Opérateurs << flux pour écrire des données
    DataParameters& operator <<(bool data);
    DataParameters& operator <<(int data);
    DataParameters& operator <<(unsigned int data);
    DataParameters& operator <<(float data);
    DataParameters& operator <<(char* data);
    DataParameters& operator <<(std::string data);

    std::map<std::string, Param>& GetParameters();

    // Variable pour réinitialiser les flux sortants
    static char* StreamReset;

private:
    // Liste des paramètres
    std::map<std::string, Param> m_params;

    // Numéro de paramètre courant
    unsigned int m_iIndex;

    // Position de lecture du stream courant
    unsigned int m_iCurrentStreamPos;
};
 
#endif

Voici le fichier .cpp :

#include "DataParameters.h"
 
/********************************* Class DataParameters *********************************/

char* DataParameters::StreamReset  = "StreamReset";

DataParameters::DataParameters() :
m_iIndex(0),
m_iCurrentStreamPos(0)
{
}
 
DataParameters::~DataParameters()
{
    m_params.clear();
}
 
DataParameters::DataParameters(int param) :
m_iIndex(0),
m_iCurrentStreamPos(0)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].i = param;
    m_iIndex++;
}
 
DataParameters::DataParameters(float param) :
m_iIndex(0),
m_iCurrentStreamPos(0)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].f = param;
    m_iIndex++;
}
 
DataParameters::DataParameters(char* param) :
m_iIndex(0),
m_iCurrentStreamPos(0)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].s = param;
    m_iIndex++;
}
 
DataParameters::DataParameters(const std::string& param) :
m_iIndex(0),
m_iCurrentStreamPos(0)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].s = param;
    m_iIndex++;
}
 
/******* Par nom *******/
 
DataParameters::DataParameters(const std::string& sParamName, int param)
{
    m_params[sParamName].i = param;
}
 
DataParameters::DataParameters(const std::string& sParamName, float param)
{
    m_params[sParamName].f = param;
}
 
DataParameters::DataParameters(const std::string& sParamName, const char* param)
{
    m_params[sParamName].s = param;
}
 
DataParameters::DataParameters(const std::string& sParamName, const std::string& sParam)
{
    m_params[sParamName].s = sParam;
}
 
/**********************************/
 
void DataParameters::setParam(const std::string& sParamName, int param)
{
    m_params[sParamName].i = param;
}
 
void DataParameters::setParam(const std::string& sParamName, float param)
{
    m_params[sParamName].f = param;
}
 
void DataParameters::setParam(const std::string& sParamName, const char* param)
{
    m_params[sParamName].s = param;
}
 
void DataParameters::setParam(const std::string& sParamName, const std::string& param)
{
    m_params[sParamName].s = param;
}
 
/*************** Getters ***************/
 
int DataParameters::getParameterAsInt(const std::string& sParamName)
{
    if (m_params.count(sParamName) > 0)
    {
        return m_params[sParamName].i;
    }
 
    return 0;
}
 
float DataParameters::getParameterAsFloat(const std::string& sParamName)
{
    if (m_params.count(sParamName) > 0)
    {
        return m_params[sParamName].f;
    }
 
    return 0.0f;
}
 
const char* DataParameters::getParameterAsString(const std::string& sParamName)
{
    if (m_params.count(sParamName) > 0)
    {
        return m_params[sParamName].s.c_str();
    }
 
    return nullptr;
}
 
DataParameters::Param* DataParameters::getParameter(const std::string& sParamName)
{
    if (m_params.count(sParamName) > 0)
    {
        return &m_params[sParamName];
    }
 
    return nullptr;
}

std::map<std::string, DataParameters::Param>& DataParameters::GetParameters()
{
    return m_params;
}

/*************** Getters sans paramètres ***************/
 
int DataParameters::getFirstParameterAsInt()
{
    if (m_params.count("0") > 0)
    {
        return m_params["0"].i;
    }
 
    return 0;
}
 
float DataParameters::getFirstParameterAsFloat()
{
    if (m_params.count("0") > 0)
    {
        return m_params["0"].f;
    }
 
    return 0.0f;
}
 
const char* DataParameters::getFirstParameterAsString()
{
    if (m_params.count("0") > 0)
    {
        return m_params["0"].s.c_str();
    }
 
    return nullptr;
}
 
DataParameters::Param* DataParameters::getFirstParameter()
{
    if (m_params.count("0") > 0)
    {
        return &m_params["0"];
    }
 
    return nullptr;
}
 
unsigned int DataParameters::getParamNumber()
{
    return m_params.size();
}
 
/****************/
 
void DataParameters::pushParam(int param)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].i = param;
    m_iIndex++;
}
 
void DataParameters::pushParam(float param)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].f = param;
    m_iIndex++;
}
void DataParameters::pushParam(const char* param)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].s = param;
    m_iIndex++;
}
 
void DataParameters::pushParam(const std::string& param)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].s = param;
    m_iIndex++;
}

//----------------------------------------
// Opérateurs >> pour lire les données
//----------------------------------------
DataParameters& DataParameters::operator >>(bool& data)
{
    std::string sParamName = std::to_string(m_iCurrentStreamPos);
 
    if (m_params.count(sParamName) > 0)
    {
        data = (m_params[sParamName].i == 0) ? 0 : 1;

        m_iCurrentStreamPos++;
    }
    else
    {
        data = false;
    }

    return *this;
}

DataParameters& DataParameters::operator >>(int& data)
{
    std::string sParamName = std::to_string(m_iCurrentStreamPos);
 
    if (m_params.count(sParamName) > 0)
    {
        data = m_params[sParamName].i;

        m_iCurrentStreamPos++;
    }
    else
    {
        data = 0;
    }

    return *this;
}

DataParameters& DataParameters::operator >>(unsigned int & data)
{
    std::string sParamName = std::to_string(m_iCurrentStreamPos);
 
    if (m_params.count(sParamName) > 0)
    {
        data = m_params[sParamName].i;

        m_iCurrentStreamPos++;
    }
    else
    {
        data = 0;
    }

    return *this;
}

DataParameters& DataParameters::operator >>(float& data)
{
    std::string sParamName = std::to_string(m_iCurrentStreamPos);
 
    if (m_params.count(sParamName) > 0)
    {
        data = m_params[sParamName].f;

        m_iCurrentStreamPos++;
    }
    else
    {
        data = 0.0f;
    }

    return *this;
}

DataParameters& DataParameters::operator >>(char* data)
{
    std::string sParamName = std::to_string(m_iCurrentStreamPos);
 
    if (m_params.count(sParamName) > 0)
    {
        data = (char*)m_params[sParamName].s.c_str();

        m_iCurrentStreamPos++;
    }
    else
    {
        data = "";
    }

    return *this;
}

DataParameters& DataParameters::operator >>(std::string& data)
{
    std::string sParamName = std::to_string(m_iCurrentStreamPos);
 
    if (m_params.count(sParamName) > 0)
    {
        data = m_params[sParamName].s;

        m_iCurrentStreamPos++;
    }
    else
    {
        data = "";
    }

    return *this;
}

//----------------------------------------
// Opérateurs << pour écrire des données
//----------------------------------------
DataParameters& DataParameters::operator <<(bool data)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].i = data;
    m_iIndex++;

    return *this;
}

DataParameters& DataParameters::operator <<(int data)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].i = data;
    m_iIndex++;

    return *this;
}

DataParameters& DataParameters::operator <<(unsigned int data)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].i = data;
    m_iIndex++;

    return *this;
}

DataParameters& DataParameters::operator <<(float data)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].f = data;
    m_iIndex++;

    return *this;
}

DataParameters& DataParameters::operator <<(char* data)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].s = data;
    m_iIndex++;

    if (data == "StreamReset")
    {
        m_iCurrentStreamPos = 0;
    }

    return *this;
}

DataParameters& DataParameters::operator <<(std::string data)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].s = data;
    m_iIndex++;

    return *this;
}

 

Résumé :

Nous avons présenté un objet rassemblant des données pouvant être de différents types.

Avec les flux << et >>  nous avons intégré une technique plus simple pour échanger des données.

Système d’Events pour gérer les événements du jeu (1ère approche)

b7ef9923828960d1a0935b08fb5ac0d2

Intro :

Dans la conception d’un jeu il est très important d’énumérer tous les mécanismes intrinsèques au comportement du jeu, c’est-à-dire des événements du gameplay, du son, de l’interface, du réseau, de la gestion des collisions et de l’IA.

Ceci est une première approche au problème.

J’ai écrit un article sur une deuxième approche plus juste et mieux adaptée.

Ici on parle des événements, nommés aussi Events.

Prérequis :

La compréhension de l’utilisation de la classe DataParameters.

Explications / exemples :

On se sert de deux macros.

Voici des macros permettant de simplifier la définition de vos Events  :


#define DECLARE_GAME_EVENT(eventName) class eventName : public Event \
      { \
          public: \
          eventName() :  \
          Event(#eventName, 0) \
          {} \
          virtual bool performTask(); \
          static const int GameEventID = Event::eventName; \
      };

#define DECLARE_GAME_EVENT_WITH_PARAMETERS(eventName, argsNumber, ...) class eventName : public Event \
      { \
          public: \
          eventName() :  \
          Event(#eventName, argsNumber, __VA_ARGS__)\
          {} \
          virtual bool performTask(); \
          static const int GameEventID = Event::eventName; \
      };

#define IMPLEMENT_GAME_EVENT(eventName) bool eventName::performTask()

Je vous fournis deux macros : une pour un Event sans paramètres et une autre pour les Events
avec paramètres.

Voici le fichier .h de la classe Event :


class Event
{
public:
    static const enum GameEvents
    {
        /* Exemples d'events à titre indicatif */
        OnTakeScreenShot,
        OnMobAggro,
        OnMobUnAggro,
        OnMobIdle,
        OnMobAttack,
        OnConsoleScrollUp,
        OnConsoleScrollDown,
        OnHeal,
        OnEnemyDamage,
        OnFocusTarget,
        OnSpellCast,
        OnMount,
        OnUnMount,
        OnGUIButtonClick,
        OnGUIButtonHover,
        OnAddItem,
        OnRemoveItem,
        OnKill,
        OnTextPaste,
        OnToggleCameraMode
    };

    Event(const char* sName, unsigned int iArgsNumber, ...);

    virtual ~Event();
    virtual void trigger();

    const std::string& getName() const;
    time_t getTimeLastTriggered() const;
    bool hasLastTriggerSucceed() const;

    // Associe un autre event à invoquer en cascade
    void pushChildEvent(Event* pEvent);

    // Paramètre associé à l'Event invoqué
    void setParameter(DataParameters& params);
    // Paramètre associé à l'Event invoqué
    DataParameters* getParameters();

    bool getLogWriting();
    void setLogWriting(bool bWrite);

    void getParametersName(std::vector<std::string>& paramsNames);

protected:
    virtual bool performTask() = 0;
    void setName(const std::string& sName);

private:
    std::string m_sName;
    time_t m_timeLastTriggered;
    bool m_bLastTriggerSucceed;

    std::vector<Event*> m_childrenEvents;

    // Une liste des noms de paramètres de l'event
    std::vector<std::string> m_parametersNames;
    DataParameters m_params;

    bool m_bLogWriting;
};

Voici le fichier .cpp


Event::Event(const char* sName, unsigned int iArgsNumber, ...) :
m_sName(sName),
m_timeLastTriggered(0),
m_bLastTriggerSucceed(false),
m_bLogWriting(true)
{
    va_list args;
    va_start(args, iArgsNumber);
    for (unsigned i = 0; i < iArgsNumber; i++)
    {
        std::string sArgName = va_arg(args, char*);

        m_parametersNames.push_back(sArgName);
    }
    va_end(args);
}

Event::~Event()
{
}

void Event::trigger()
{       
    time(&m_timeLastTriggered);
    m_bLastTriggerSucceed = performTask();

    if (getLogWriting())
    {
        //SYSTEM_LOG->log(Logger::MessageType::MSG_TYPE_EVENT, std::string("Event effectué : " + getName()).c_str());
    }

    // Invoque aussi les events enfants et associés (en cascade) :
    for (unsigned int i = 0; i < m_childrenEvents.size(); i++)
    {
        Event* pEvent = m_childrenEvents[i];
        
        if (pEvent)
            pEvent->trigger();
    }
}

const std::string& Event::getName() const
{
    return m_sName;
}

time_t Event::getTimeLastTriggered() const
{
    return m_timeLastTriggered;
}

bool Event::hasLastTriggerSucceed() const
{
    return m_bLastTriggerSucceed;
}

void Event::setName(const std::string& sName)
{
    m_sName = sName;
}

void Event::pushChildEvent(Event* pEvent)
{
    if (pEvent)
    {
        m_childrenEvents.push_back(pEvent);
    }
}

void Event::setParameter(DataParameters& params)
{
    m_params = params;
}

DataParameters* Event::getParameters()
{
    return &m_params;
}

bool Event::getLogWriting()
{
    return m_bLogWriting;
}

void Event::setLogWriting(bool bWrite)
{
    m_bLogWriting = bWrite;
}

void Event::getParametersName(std::vector<std::string>& paramsNames)
{
    paramsNames = m_parametersNames;
}

On définit les Events comme suit :


DECLARE_GAME_EVENT( OnTakeScreenShot );
DECLARE_GAME_EVENT( OnMobAggro );
DECLARE_GAME_EVENT( OnMobUnAggro );
DECLARE_GAME_EVENT( OnMobIdle );
DECLARE_GAME_EVENT( OnMobAttack );
DECLARE_GAME_EVENT( OnConsoleScrollUp );
DECLARE_GAME_EVENT( OnConsoleScrollDown );
DECLARE_GAME_EVENT( OnHeal );
DECLARE_GAME_EVENT( OnEnemyDamage );
DECLARE_GAME_EVENT( OnFocusTarget );
DECLARE_GAME_EVENT( OnSpellCast );
DECLARE_GAME_EVENT( OnMount );
DECLARE_GAME_EVENT( OnUnMount );
DECLARE_GAME_EVENT( OnGUIButtonClick );
DECLARE_GAME_EVENT( OnGUIButtonHover );
DECLARE_GAME_EVENT( OnAddItem );
DECLARE_GAME_EVENT( OnRemoveItem );
DECLARE_GAME_EVENT( OnKill );
DECLARE_GAME_EVENT( OnTextPaste );
DECLARE_GAME_EVENT( OnToggleCameraMode );

Et on implémente les Events comme suit :


IMPLEMENT_GAME_EVENT( OnTextPaste )
{
    if (OpenClipboard(NULL))
    {
        HANDLE hData = GetClipboardData(CF_TEXT);

        if (hData)
        {
            char* pszText = static_cast<char*>(GlobalLock(hData));

            // Release the lock
            GlobalUnlock( hData );
            // Release the clipboard
            CloseClipboard();

            if (strlen(pszText) > 100)
            {
                return true;
            }

            switch (GAME_STATE_MANAGER->getState())
            {
                case GameState::ON_CONSOLE:
                    CONSOLE->appendCommandLineText(pszText);
                    break;

                case GameState::ON_LOADING_MENU:
                    break;
            }

        }
    }

    return true;
}

IMPLEMENT_GAME_EVENT( OnCameraMovementUpwardRight  )
{
    switch (GAME_STATE_MANAGER->getState())
    {
        case GameState::ON_WORLD:
            getGlobalSystems()->
                 getCameraManager()->setCameraMovement(CameraManager::CAMERA_MOVE_UPWARD |
                                                       CameraManager::CAMERA_STRAFE_RIGHT);
            break;
    }

    return true;
}

Voici le fichier EventManager.h :


class Event;
class DataParameters;

class EventManager
{
public:
    EventManager();
    virtual ~EventManager();
    
    void registerEvent(Event* const pEvent);

    // Invoque un event avec spécification de paramètres
    void triggerEvent(const std::string& sEventName);
    // Invoque un event
    void triggerEvent(const std::string& sEventName, DataParameters& p);

    // On invoque la fonction callback depuis un fichier script lua
    void triggerLuaCallback(const std::string& sEventName, DataParameters& p);
    void triggerLuaCallback(const std::string& sEventName);

    // On peut parenter un event

    void attachEventTo(Event* pEventToAttach, Event* pChildEvent);

    Event* getEvent(const std::string& sEventName);

    void setParametersToEvent(std::string sEventName, DataParameters pParam);

    void getEventsNames(std::vector<std::string>& paramNames);
    
    // On peut invoquer des events spécicifiques à travers une pile d'appel
    void pushEvent(const std::string& sName);
    void triggerPushedEvents();
    void clearPushedEvent();

private:
    // La liste des events
    std::map<std::string, Event*> m_events;
    std::map<std::string, Event*> m_pushedEvents;

};

Voici le fichier EventManager.cpp

EventManager::EventManager()
{
}

EventManager::~EventManager()
{
}

void EventManager::registerEvent(Event* const pEvent)
{
    SKIP_IF_FAILED_AssertMsg(pEvent != NULL, "pEvent NULL")
    {
        std::string sName = pEvent->getName();
        
        SKIP_IF_FAILED_AssertMsg(m_events.count(sName) == 0, "Event déjà enregistré")
        {
            m_events[sName] = pEvent;
        }
    }
}

// On déclenche sans paramètre
void EventManager::triggerEvent(const std::string& sEventName)
{
    if (m_events.count(sEventName) > 0)
    {
        Event* pEvent = m_events[sEventName];
        pEvent->trigger();

        triggerLuaCallback(sEventName);
    }
}

// On déclenche avec un paramètre
void EventManager::triggerEvent(const std::string& sEventName, DataParameters& p)
{
    if (m_events.count(sEventName) > 0)
    {
        Event* pEvent = m_events[sEventName];
        pEvent->setParameter(p);

        pEvent->trigger();

        triggerLuaCallback(sEventName, p);
    }
}

void EventManager::triggerLuaCallback(const std::string& sEventName)
{
    // todo
}

void EventManager::triggerLuaCallback(const std::string& sEventName, DataParameters& p)
{
    // todo
}

void EventManager::attachEventTo(Event* pEventToAttach, Event* pChildEvent)
{
    if (pEventToAttach && pChildEvent)
    {
        pEventToAttach->pushChildEvent(pChildEvent);
    }
}

Event* EventManager::getEvent(const std::string& sEventName)
{
    if (m_events.count(sEventName) > 0)
    {
        Event* pEvent = m_events[sEventName];
        return pEvent;
    }
    else
    {
        return NULL;
    }
}

void EventManager::setParametersToEvent(std::string sEventName, DataParameters pParam)
{
    Event* pEvent = getEvent(sEventName);

    SKIP_IF_FAILED_AssertMsg(pEvent == NULL, "pEvent est NULL")
    {
        pEvent->setParameter(pParam);
    }
}

void EventManager::getEventsNames(std::vector<std::string>& paramNames)
{
    std::map<std::string, Event*>::iterator it;

    for (it = m_events.begin(); it != m_events.end(); it++)
    {
        paramNames.push_back((*it).first);
    }
}

void EventManager::pushEvent(const std::string& sName)
{
    Event* pEvent = getEvent(sName);

    AssertNULLPointer(pEvent);

    std::string sEventName = pEvent->getName();
        
    m_pushedEvents[sEventName] = pEvent;
}

void EventManager::clearPushedEvent()
{
    m_pushedEvents.clear();
}

void EventManager::triggerPushedEvents()
{
    std::map<std::string, Event*>::iterator it;

    for (it = m_pushedEvents.begin(); it != m_pushedEvents.end(); it++)
    {
        Event* pEvent = it->second;

        pEvent->trigger();
    }
}

On enregistres les Events comme ci :

 

EVENT_MANAGER->registerEvent( new OnTextPaste() );
EVENT_MANAGER->registerEvent( new OnCameraMovementUpwardRight() );

Résumé :

L’implémentation d’un système d’Event est cruciale dans toute conception de jeu vidéo. Elle permet en effet de modulariser les mécanismes de jeu et rend le code plus élégant et plus facile à comprendre.

Classe BoundingVolumeTrigger pour vos triggers

1

Intro :

Dans un jeu vidéo on doit utiliser beaucoup de bounding boxes pour déclencher certains événements.

Prérequis :

Si vous ne savez pas ce qu’est une Bounding Box : se référer à l’article traitant les Bounding Boxes.
Si vous ne savez pas ce qu’est un Event : se référer à l’article traitant les Events.

Utilisation :

Par exemple lorsque que le joueur entre dans une nouvelle zone, on a besoin de tester deux bounding boxes : celle du joueur et celle du trigger (déclencheur), pour ensuite faire appel à l’événement (Event) associé.

Voici une classe à copier pour implémenter le mécanisme de triggers dans vos jeux.

Dans le fichier .h :


#ifndef BOUNDING_BOX_TRIGGER_H
#define BOUNDING_BOX_TRIGGER_H

class BoundingVolumeTrigger
{
public:
    BoundingVolumeTrigger(const Ogre::AxisAlignedBox& BV);
    virtual ~BoundingVolumeTrigger();

    // AABB / Sphere principale à tester
    void registerMainCheckedBV(const Ogre::AxisAlignedBox& BV);

    // Les AABB / Sphere sur lequels sont testées les intersections de AABB / Sphere
    void registerBoundingBox(const Ogre::AxisAlignedBox& BV);    

    // Les events à invoquer lors d'une collision
    void registerEvent(Event* pEvent);

    bool checkCollision();

    // Met à jour les vérification de déclenchement (triggering)
    void update();

private:
    Ogre::AxisAlignedBox m_testedAABB;

    std::vector<const Ogre::AxisAlignedBox> m_boundingBoxesToTest;

    std::vector<Event*> m_pCollisionEvents;

    bool m_bTriggerDone;
};

#endif

Dans le fichier .cpp :


BoundingVolumeTrigger::BoundingVolumeTrigger(const Ogre::AxisAlignedBox& BV)
{
    m_bTriggerDone = false;
    m_testedAABB = BV;
}

BoundingVolumeTrigger::~BoundingVolumeTrigger()
{
}

void BoundingVolumeTrigger::registerMainCheckedBV(const Ogre::AxisAlignedBox& BV)
{
    m_testedAABB = BV;
}

void BoundingVolumeTrigger::registerBoundingBox(const Ogre::AxisAlignedBox& BV)
{
    m_boundingBoxesToTest.push_back(BV);
}

void BoundingVolumeTrigger::registerBoundingSphere(const Ogre::Sphere& BV)
{
    m_boundingSpheresToTest.push_back(BV);
}

void BoundingVolumeTrigger::registerEvent(Event* pEvent)
{
    m_pCollisionEvents.push_back(pEvent);
}

void BoundingVolumeTrigger::update()
{
    bool bColision = checkCollision();

    // Invoque tous les events associés à la collision
    if (bColision && !m_bTriggerDone)
    {
        for (unsigned int i = 0; i < m_pCollisionEvents.size(); i++)
        {
            Event* pEvent = m_pCollisionEvents[i];
            
            pEvent->trigger();
        }

        m_bTriggerDone = true;
    }
}

bool BoundingVolumeTrigger::checkCollision()
{
    bool collided = false;

    // AABB à tester
    for (unsigned int i = 0; i < m_boundingBoxesToTest.size(); i++)
    {
        if (m_testedAABB.contains(m_boundingBoxesToTest[i]))
        {
            collided = true;

        }
    }

    return collided;
}

La variable m_bTriggerDone restreint l’invocation du déclencheur à un seul appel par rencontre
de collision.

Résumé : 

Le Singleton

Intro :

Le Singleton est un patron de conception.

Il est utile lorsque l’on a besoin d’utiliser et d’instancier un seul objet au lieu le passer paramètre après paramètre et fonction par fonction.

En somme il permet d’accéder directement dans tout le programme un objet quelconque (appelé « Singleton »).

Explications / exemples :

Cas sans le Singleton :


// on passe le paramètre fonction après fonction
void une_foncton(Object* obj)
{
    une_autre_fonction(obj);
}

// et encore un paramètre de passé...
void une_autre_fonction(Object* obj)
{
    std::string sName = obj->getName();

    std::cout << sName << std::endl;
}

Cas avec le Singleton :

On accède directement à l’objet sans avoir à le passer paramètre par paramètre :


void une_autre_fonction()
{
    Object->getSingleton()->getName();
}

Donc  :


// Cette fonction n'est plus utile
void une_foncton(Object* obj) 


Voici le fichier d’en-tête / d’include :


#ifndef SINGLETON_H
#define SINGLETON_H

#define SAFE_DELETE(p) if (p) { delete (p); (p) = NULL; }

template <typename T>
class Singleton
{
public:
    Singleton()
    {
        assert(!ms_instance);

        ms_instance = static_cast<T*>(this);
    }

    ~Singleton () {}

    static T* getSingletonPtr()
    {
        assert(ms_instance);

        return ms_instance;
    }

    static void destroy()
    {
        SAFE_DELETE(ms_instance);
    }

    static bool IsCreated()
    {
        return ms_instance != nullptr;
    }

private: // Opération interdite
    Singleton(const Singleton<T> &);
    Singleton& operator=(const Singleton<T> &);

private:
    static T* ms_instance;
};

#endif

 

Utilisation :

Dans le fichier include : EventManager.h


class EventManager : public Singleton<EventManager>
{
    // Suite de votre code...
};

Dans le fichier cpp : EventManager.cpp

on doit mettre tout en haut de ce fichier :


template<> EventManager* Singleton<EventManager>::ms_instance = NULL;


Exemple d’utilisation :


GlobalSystems::GlobalSystems()
{
    m_pInputManager = InputManager::getSingletonPtr();
    m_pTextAnnouncer = TextAnnouncer::getSingletonPtr();
    m_pCameraManager = CameraManager::getSingletonPtr();
    m_pConsole = Console::getSingletonPtr();
}

Astuces et conseils :

warning

1 – N’oubliez surtout pas de créer votre objet avant son utilisation en appelant :


new NomDeLaClasse();

sinon vous obtiendrez un bug de pointeur non initialisé ! (segfault)

2 – Utiliser plusieurs macro #define de la sorte :


#define EVENT_MANAGER EventManager->getSingleton()

on utilise cette macro / raccourcis pour appeler une méthode : EVENT_MANAGER->methode()
cela permet de simplifier l’écriture du code…

Résumé :

Le patron de conception Singleton restreint l’instanciation d’une classe à un seul objet.
Il permet de simplifier l’accès global à un objet (par exemple à une classe TextureManager ou EventManager)

Initialiser rapidement DirectX 10.1 avec DXUT

1342671301_50721

Intro :

DXUT est un framework qui vous permet d’initialiser de manière plus rapide et plus simple DirectX 10 à travers des fonctions toutes prêtes.

Prérequis :

Savoir un peu initialiser DirectX.

Exemples :

Voici un exemple d’utilisation :


int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )
{
    DXUTSetCallbackD3D10DeviceAcceptable( IsD3D10DeviceAcceptable );
    DXUTSetCallbackD3D10DeviceCreated( OnD3D10CreateDevice );
    DXUTSetCallbackD3D10SwapChainResized( OnD3D10ResizedSwapChain );
    DXUTSetCallbackD3D10SwapChainReleasing( OnD3D10ReleasingSwapChain );
    DXUTSetCallbackD3D10DeviceDestroyed( OnD3D10DestroyDevice );
    DXUTSetCallbackD3D10FrameRender( OnD3D10FrameRender );

    DXUTSetCallbackMsgProc( MsgProc );
    DXUTSetCallbackKeyboard( OnKeyboard );
    DXUTSetCallbackFrameMove( OnFrameMove );
    DXUTSetCallbackDeviceChanging( ModifyDeviceSettings );

    DXUTInit( true, true, NULL );
    DXUTSetCursorSettings( true, true );
    DXUTCreateWindow( L"DXUT - Introduction" );
    DXUTCreateDevice( true, 640, 480 );
    DXUTMainLoop();

    return DXUTGetExitCode();
}

On remarque bien que quelques lignes suffisent pour démarrer DirectX 10 avec DXUT.

Vous remarquerez aussi que l’on insère des fonctions en tant que paramètres : ce sont des fonctions callbacks (c’est-à-dire des fonctions qui sont appelées de manière interne par DirectX).

Explications :

Initialisation :


DXUTInit(bool, bool, WCHAR*, bool);

Le premier argument indique à DXUT d’interprêter ou non les arguments passés en ligne de comande :

BasicHLSL.exe -windowed -width:400 -height:300

Le deuxième argument indique à DXUT d’afficher ou non une boite de dialogue s’il une erreur à lieu.

Les deux autres arguments ne sont pas nécessaires pour comprendre.

Curseur de la souris :

Voici l’instruction pour configurer le curseur de souris.

DXUTSetCursorSettings(bool, bool);

Le premier paramètre indique à DXUT que le curseur doit ou non
être affichée en mode plein écran.

Le premier paramètre indique à DXUT que le curseur doit
être restreint ou non de traverser les bords de la fenêtre en mode plein écran.

Création d’une fenêtre :


DXUTCreateWindow(const WCHAR, HINSTANCE, HICON, HMENU, INT, INT);

Le premier argument spécifie le titre de la fenêtre.

Le deuxième argument spécifie l’handle de la fenêtre (on peut le mettre à NULL).

Le troisième argument spécifie à la fenêtre quelle icone utiliser (on peut le mettre à NULL aussi).

Les deux autres arguments ne sont pas nécessaires pour comprendre.

Création du device :


DXUTCreateDevice(bool, int, int); 

Le premier argument indique ou non si la fenêtre créée doit être en plein écran ou fenêtrée.

Les deux autres arguments indiquent la résolution (au mieux) de la fenêtre de rendu.

Par ailleurs, après cette instruction, DXUT choisira automatiquement la version de DirectX : D3D9 ou D3D10, selon les capacités du matériel.

Rendu :

Au final l’Instruction pour démarrer la boucle de rendu :


DXUTMainLoop();


Les fonctions Callbacks :

Maintenant pour utiliser DXUT on doit créer un fichier Callbacks.h où mettrez le code des fonctions nécessaire à DXUT :


ID3D10InputLayout* g_pVertexLayout = NULL;

struct SimpleVertex
{
    D3DXVECTOR3 Pos;
    D3DXVECTOR3 Colour;
};

bool CALLBACK IsD3D10DeviceAcceptable(UINT, UINT, D3D10_DRIVER_TYPE,
                                       DXGI_FORMAT, bool, void*);

HRESULT CALLBACK OnD3D10CreateDevice(ID3D10Device*, const DXGI_SURFACE_DESC*,
                                      void*);

HRESULT CALLBACK OnD3D10ResizedSwapChain(ID3D10Device*, IDXGISwapChain*,
                                          const DXGI_SURFACE_DESC*, void*);

void CALLBACK OnD3D10FrameRender(ID3D10Device*, double, float, void*);

void CALLBACK OnD3D10ReleasingSwapChain(void*);

void CALLBACK OnD3D10DestroyDevice(void*);

LRESULT CALLBACK MsgProc(HWND, UINT, WPARAM, LPARAM,
                         bool*, void*);

void CALLBACK OnKeyboard(UINT, bool, bool, void*);

void CALLBACK OnFrameMove(double, float, void*);

bool CALLBACK ModifyDeviceSettings(DXUTDeviceSettings*, void*);

 

Vous mettrez toutes les fonctions énumérées ici prochainement dans un fichier DXUTIntroduction.cpp

La première fonction callback :


// N'est pas utilisée
bool CALLBACK IsD3D10DeviceAcceptable(UINT Adapter, UINT Output, D3D10_DRIVER_TYPE DeviceType,
                                      DXGI_FORMAT BufferFormat, bool bWindowed, void* pUserContext)
{
    return true;
}

 

La deuxième fonction callback :

// Initialize le rendu DirectX
HRESULT CALLBACK OnD3D10CreateDevice(ID3D10Device* pd3dDevice, const DXGI_SURFACE_DESC* pBufferSurfaceDesc,
                                      void* pUserContext)
{
    HRESULT hr = S_OK;

    // Définit le schéma d'entrée
    D3D10_INPUT_ELEMENT_DESC layout[] =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    };
    UINT numElements = sizeof(layout) / sizeof(layout[0]);

    // Créé le schéma d'entrée
    D3D10_PASS_DESC PassDesc;
    g_pTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);
    V_RETURN( pd3dDevice->CreateInputLayout(layout, numElements, PassDesc.pIAInputSignature,
                                             PassDesc.IAInputSignatureSize, &g_pVertexLayout));

    // Set the input layout
    pd3dDevice->IASetInputLayout(g_pVertexLayout);

    SimpleVertex vertices[] =
    {
        { D3DXVECTOR3( -1.0f, 1.0f, -1.0f ), D3DXVECTOR4( 0.0f, 0.0f, 1.0f, 1.0f ) },
        { D3DXVECTOR3( 1.0f, 1.0f, -1.0f ), D3DXVECTOR4( 0.0f, 1.0f, 0.0f, 1.0f ) },
        { D3DXVECTOR3( 1.0f, 1.0f, 1.0f ), D3DXVECTOR4( 0.0f, 1.0f, 1.0f, 1.0f ) },
        { D3DXVECTOR3( -1.0f, 1.0f, 1.0f ), D3DXVECTOR4( 1.0f, 0.0f, 0.0f, 1.0f ) },
        { D3DXVECTOR3( -1.0f, -1.0f, -1.0f ), D3DXVECTOR4( 1.0f, 0.0f, 1.0f, 1.0f ) },
        { D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR4( 1.0f, 1.0f, 0.0f, 1.0f ) },
        { D3DXVECTOR3( 1.0f, -1.0f, 1.0f ), D3DXVECTOR4( 1.0f, 1.0f, 1.0f, 1.0f ) },
        { D3DXVECTOR3( -1.0f, -1.0f, 1.0f ), D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 1.0f ) },
    };

    D3D10_BUFFER_DESC bd;
    bd.Usage = D3D10_USAGE_DEFAULT;
    bd.ByteWidth = sizeof(SimpleVertex) * 24;
    bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = 0;
    bd.MiscFlags = 0;
    D3D10_SUBRESOURCE_DATA InitData;
    InitData.pSysMem = vertices;
    V_RETURN( pd3dDevice->CreateBuffer(&bd, &InitData, &g_pVertexBuffer));

    // Définit le vertex buffer
    UINT stride = sizeof(SimpleVertex);
    UINT offset = 0;
    pd3dDevice->IASetVertexBuffers(0, 1, &g_pVertexBuffer, &stride, &offset);

    // Set primitive topology
    pd3dDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    return S_OK;
}

 

La troisième fonction callback :

HRESULT CALLBACK OnD3D10ResizedSwapChain(ID3D10Device* pd3dDevice, IDXGISwapChain* pSwapChain,
                                          const DXGI_SURFACE_DESC* pBufferSurfaceDesc, void* pUserContext)
{
    return S_OK;
}

 

La quatrième fonction callback :

// Appelée à chaque étape de boucle de rendu
void CALLBACK OnD3D10FrameRender(ID3D10Device* pd3dDevice, double fTime,
                                 float fElapsedTime, void* pUserContext)
{
    float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f };
    ID3D10RenderTargetView* pRTV = DXUTGetD3D10RenderTargetView();
    pd3dDevice->ClearRenderTargetView( pRTV, ClearColor );

    ID3D10DepthStencilView* pDSV = DXUTGetD3D10DepthStencilView();
    pd3dDevice->ClearDepthStencilView( pDSV, D3D10_CLEAR_DEPTH, 1.0, 0 );

    pd3dDevice->Draw(3, 0);
}

 

La cinquième fonction callback :

// N'est pas utilisée
void CALLBACK OnD3D10ReleasingSwapChain(void* pUserContext)
{
}

 

La sixième fonction callback :


// Détruit les composants de DirectX
void CALLBACK OnD3D10DestroyDevice( void* pUserContext )
{
    g_pVertexLayout->Release();
    g_pVertexLayout = NULL.
}

 

La septième fonction callback :


// N'est pas utilisée
bool CALLBACK ModifyDeviceSettings( DXUTDeviceSettings* pDeviceSettings, void* pUserContext )
{
    return true;
}

 

La huitième fonction callback :


LRESULT CALLBACK MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool* pbNoFurtherProcessing,
                          void* pUserContext )
{
    return 0;
}

 

La dernière fonction callback :


void CALLBACK OnKeyboard( UINT nChar, bool bKeyDown, bool bAltDown, void* pUserContext )
{
    if( bKeyDown )
    {
        switch( nChar )
        {
            case VK_F1: // Change as needed                
                break;
        }
    }
}

 

Résumé :

DXUT vous permet de restreindre d’initialiser de manière laborieuse DirectX 10.
Vous pouvez retrouver le code source de ce petit tutoriel ici.

Références :

Documentation DirectX SDK June 2010

Un système de Process et de ProcessManager

 

WoWScrnShot_082916_183045

Intro :

Dans un jeu vidéo on fait souvent appel à plusieurs sous-systèmes et pour les mettre à jour fréquemment.

On va utiliser deux classes permettant de gérer ces sous systèmes.

class Process : classe de base, on peut la dériver puis redéfinir la méthode VUpdate().
class ProcessManager : gestionnaire d’instances de Process.

Un Process peut être n’importe quoi : il faut juste tout simplement que ce soit un objet qui nécessite d’être mise à jour.

Par exemple on peut l’utiliser pour : le système de script, des collisions, de l’audio, de l’IA, du rendu de certaines entités.

Si on veut mettre le jeu en pause il  suffira de faire appel aux méthodes Pause() et UnPause().

Pour le fichier include Process.h


class Process
{
    friend class ProcessManager;

public:
    enum State
    {
        UNINITIALIZED = 0,
        REMOVED,
        RUNNING,
        PAUSED,
        SUCCEEDED,
        FAILED,
        ABORTED,
    };
    
private:
    State m_state; 

public:

    Process(void);
    virtual ~Process(void);
    
protected:
    virtual void VOnInit(void) { m_state = RUNNING; }
    virtual void VOnUpdate(unsigned long deltaMs) = 0;
    virtual void VOnSuccess(void) { }
    virtual void VOnFail(void) { }
    virtual void VOnAbort(void) { }

public:
    inline void Succeed(void);
    inline void Fail(void);
    
    inline void Pause(void);
    inline void UnPause(void);
    State GetState(void) const { return m_state; }
    bool IsAlive(void) const { return (m_state == RUNNING
                               || m_state == PAUSED); }
    bool IsDead(void) const { return (m_state == SUCCEEDED
                              || m_state == FAILED
                              || m_state == ABORTED); }
    bool IsRemoved(void) const { return (m_state == REMOVED); }
    bool IsPaused(void) const { return m_state == PAUSED; }

    inline void AttachChild(Process* pChild);
    Process* RemoveChild(void);
    Process* PeekChild(void) { return m_pChild; }

private:
    void SetState(State newState) { m_state = newState; }
};

Dans le fichier Process.cpp :


Process::Process(void)
{
    m_state = UNINITIALIZED;
}

Process::~Process(void)
{
    if (m_pChild)
    {
        m_pChild->VOnAbort();
    }
}

inline void Process::Succeed(void)
{
    assert(m_state == RUNNING || m_state == PAUSED);
    m_state = SUCCEEDED;
}

inline void Process::Fail(void)
{
    assert(m_state == RUNNING || m_state == PAUSED);
    m_state = FAILED;
}

inline void Process::AttachChild(StrongProcessPtr pChild)
{
    if (m_pChild)
        m_pChild->AttachChild(pChild);
    else
        m_pChild = pChild;
}

inline void Process::Pause(void)
{
    if (m_state == RUNNING)
        m_state = PAUSED;
    else
        std::cout <<
        "Attempting to pause a process that isn't running"
        << std::endl;
}

inline void Process::UnPause(void)
{
    if (m_state == PAUSED)
        m_state = RUNNING;
    else
        std::cout <<
        "Attempting to unpause a process that isn't paused"
        << std::endl;
}

Process* Process::RemoveChild(void)
{
    if (m_pChild)
    {
        m_pChild = NULL;
    }

    return NULL;
}

Maintenant pour le fichier include ProcessManager.h :


#include "Process.h"

class ProcessManager
{
    typedef std::list<Process*> ProcessList;

    ProcessList m_processList;

public:

    ~ProcessManager(void);

    unsigned int UpdateProcesses(unsigned long deltaMs);
    Process* AttachProcess(Process* pProcess);
    void AbortAllProcesses(bool immediate);

    unsigned int GetProcessCount(void) const {
                 return m_processList.size(); }

private:
    void ClearAllProcesses(void);
};

Pour le fichier ProcessManager.cpp :


ProcessManager::~ProcessManager(void)
{
    ClearAllProcesses();
}

unsigned int ProcessManager::UpdateProcesses(unsigned long deltaMs)
{
    unsigned short int successCount = 0;
    unsigned short int failCount = 0;

    ProcessList::iterator it = m_processList.begin();
    while (it != m_processList.end())
    {
        Process* pCurrProcess = (*it);

        ProcessList::iterator thisIt = it;
        ++it;

        if (pCurrProcess->GetState() == Process::UNINITIALIZED)
            pCurrProcess->VOnInit();

        if (pCurrProcess->GetState() == Process::RUNNING)
            pCurrProcess->VOnUpdate(deltaMs);

        if (pCurrProcess->IsDead())
        {
            switch (pCurrProcess->GetState())
            {
                case Process::SUCCEEDED :
                {
                    pCurrProcess->VOnSuccess();
                    Process* pChild = pCurrProcess->RemoveChild();
                    if (pChild)
                        AttachProcess(pChild);
                    else
                        ++successCount;
                    break;
                }

                case Process::FAILED :
                {
                    pCurrProcess->VOnFail();
                    ++failCount;
                    break;
                }

                case Process::ABORTED :
                {
                    pCurrProcess->VOnAbort();
                    ++failCount;
                    break;
                }
            }

            m_processList.erase(thisIt);
        }
    }

    return ((successCount << 16) | failCount);
}

Process* ProcessManager::AttachProcess(Process* pProcess)
{
    m_processList.push_front(pProcess);
    return pProcess;
}

void ProcessManager::ClearAllProcesses(void)
{
    m_processList.clear();
}

void ProcessManager::AbortAllProcesses(bool immediate)
{
    ProcessList::iterator it = m_processList.begin();
    while (it != m_processList.end())
    {
        ProcessList::iterator tempIt = it;
        ++it;

        StrongProcessPtr pProcess = *tempIt;
        if (pProcess->IsAlive())
        {
            pProcess->SetState(Process::ABORTED);
            if (immediate)
            {
                pProcess->VOnAbort();
                m_processList.erase(tempIt);
            }
        }
    }
}


Résumé :

Ces classes vous seront utiles pour implémenter vos propres systèmes / process.

Références :

– Game Coding Complete 4 (code LGPL)

Les vecteurs

vector_2d_coordinates

Intro :

Ceci constitue un petit cours sur les vecteurs que vous avez vus normalement au lycée.

Un vecteur est une entité mathématique qui désigne aussi bien une position qu’une direction dans un repère donné.
Un vecteur peut servir à représenter une force, une position, une vitesse, par exemple la direction à laquelle le joueur regarde, etc…

Dans cet article on notera un vecteur {v} plutôt que \vec{v} .

Prérequis :

– Avoir déjà lu et compris quelques rudiments sur les mathématiques des vecteurs.

Explications :

Un vecteur se note de cette façon : v=({x,y,z})

Opérations sur les vecteurs :

Obtenir la négation d’un vecteur :

-v=-({x,y,z}) = ({-x,-y,-z})

 

On peut ajouter deux vecteurs :

{v + w} = {({v_{x},v_{y},v_{z}}) + ({w_{x},w_{y},w_{z}})} = ({v_{x} + w_{x},v_{y} + w_{y},v_{z} + w_{z}})

 

Soustraire deux vecteurs :

{v - w} = {v + (-w)} = {({v_{x},v_{y},v_{z}}) + ({-w_{x},-w_{y},-w_{z}})} = ({v_{x} - w_{x},v_{y} - w_{y},v_{z} - w_{z}})

 

Multiplier un vecteur par un scalaire :

\textbf{k}v = ({\textbf{k}v_{x}, \textbf{k}v_{y}, \textbf{k}v_{z}})

 

On peut inverser la direction d’un vecteur dans le même genre :

{(\textbf{-1})}v = -v = ({-v_{x}, -v_{y}, -v_{z}})

 

On peut calculer la longueur d’un vecteur (en utilisant le théorème de Pythagore) :

\|v\| = \sqrt{{v}_x^2+{v}_y^2+{v}_z^2}

 

On peut calculer la distance entre deux vecteurs :

distance({a}, {b})=\|{b}-{a}\|=\sqrt{({b}_x-{a}_x)^{2}+({b}_y-{a}_y)^{2}+({b}_z-{a}_z)^{2}}

 

Normaliser un vecteur (rendre sa longueur de 1 unité) :

{v}_{norm}=\frac{{v}}{\|{v}\|}

 

Il existe aussi deux autres opérations qui sont : le produit scalaire et le produit vectoriel.

L’opération du produit scalaire donne comme résultat un nombre (d’où le nom « scalaire ») :

{v}\cdot{w}={v}_{x}{w}_{x}+{v}_{y}{w}_{y}+{v}_{z}{w}_{z}

 

Il s’exprime aussi avec la fonction cosinus avec comme paramètre l’angle entre les deux vecteurs :

{v}\cdot{w}=\|{v}\|\|{w}\|\cos(\theta)

 

Le produit scalaire est une opération fondamentale dans le calcul des lumières dans le rendu graphique 3D.

On peut trouver l’angle entre les deux vecteurs ainsi :

\theta=\arccos\left(\frac{{a}\cdot{b}}{\|{a}\|\|{b}\|}\right)

 

La fonction arccos est tout simplement la fonction cos⁻¹ de votre calculatrice.

L’opération du produit vectoriel :

{w}={u}\times{v}

 

Le résultat du produit vectoriel est un vecteur perpendiculaire aux deux vecteurs (u et v) de l’opération ; le sens de ce vecteur dépend de l’ordre des opérandes dans la multiplication.

On le calcul d’après la formule suivante :

{w}={u}\times{v}=(({u}_{y}{v}_{z}-{u}_{z}{v}_{y}),({u}_{z}{v}_{x}-{u}_{x}{v}_{z}),({u}_{x} {v}_{y}-{u}_{y}{v}_{x}))


Résumé :

Nous avons vu la notion de vecteur ; objet mathématique fondamentale dans la réalisation d’un jeu vidéo en 2D ou 3D.

Fonction à nombres variables d’arguments

Intro :

Il peut être utile de passer un nombre indéfini de paramètres à une fonction.

Il faut inclure au minimum un paramètre obligatoire. Ensuite on peut passer un nombre indéfini d’arguments à la fonction.

Exemple  :

    void nomFonction(int parametreA, ...);

Les trois petits points (…) indique au compilateur que la fonction peut prendre plusieurs arguments.

Il faut inclure <stdarg.h> pour accéder aux fonctions et macros de manipulation des paramètres variables.

On doit indiquer en paramètre le nombre d’arguments passés par cette fonction ! (ici « int n »)

#include <stdarg.h>

void f (int n, ...)
{
   va_list va;
   va_start (va, n);

   for (unsigned int i = 0; i < n; i++)
   {
      char* sArgName = va_arg(va, char*);
   }

   va_end (va);
}

Ici on récupère des variables de type char* (chaine de caractères).

Résumé :

Les fonctions qui peuvent prendre un nombre variable d’arguments ne sont pas toujours utilisées mais peuvent toujours être utiles.

Récupérer les informations du système

Intro :

2000px-Computer-aj_aj_ashton_01.svg

Cette classe va vous permettre de récupérer les informations de votre système d’exploitation, du nom de votre processeur et de sa vitesse, du nombre de mémoire que vous disposez…


Explications :


std::string getOsName();
std::string getCpuName();
std::string getCpuSpeed();
std::string getTotalMemory();
std::string getAvailableMemory();

Dans le fichier ComputerInfo.h :

#ifndef COMPUTER_INFO_H
#define COMPUTER_INFO_H

class ComputerInfo
{
public:
    ComputerInfo();
    ~ComputerInfo();

    void parseOSName();
    void parseCPU();
    void parseMemory();
    void parseHDSpace();
    
    std::string getOsName();
    std::string getCpuName();
    std::string getCpuSpeed();
    std::string getTotalMemory();
    std::string getAvailableMemory();

private:
    std::string m_sOsName;
    std::string m_sCpuName;
    std::string m_sCpuSpeed;
    std::string m_sTotalMemory;
    std::string m_sAvailableMemory;
};

#endif

Dans votre fichier ComputerInfo.cpp :

#include <direct.h>
#include "ComputerInfo.h"

ComputerInfo::ComputerInfo()
{
    parseOSName();
    parseCPU();
    parseMemory();
}

ComputerInfo::~ComputerInfo()
{
}

std::string ComputerInfo::getOsName()
{
    return m_sOsName;
}

std::string ComputerInfo::getCpuName()
{
    return m_sCpuName;
}

std::string ComputerInfo::getCpuSpeed()
{
    return m_sCpuSpeed;
}

std::string ComputerInfo::getTotalMemory()
{
    return m_sTotalMemory;
}

std::string ComputerInfo::getAvailableMemory()
{
    return m_sAvailableMemory;
}

void ComputerInfo::parseOSName()
{
    OSVERSIONINFO osvi;
    ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx(&osvi);

    std::string sOSName;

    switch(osvi.dwMajorVersion)
    {
        case 6:
            {
                if (osvi.dwMinorVersion == 3)
                    sOSName = "Windows 8.1";
                else if (osvi.dwMinorVersion == 2)
                    sOSName = "Windows 8";
                else if (osvi.dwMinorVersion == 1)
                    sOSName = "Windows 7";
                else if (osvi.dwMinorVersion == 0)
                    sOSName = "Windows Vista";
            }
            break;
        case 5:
            {
                if (osvi.dwMinorVersion == 1)
                    sOSName = "Windows XP";
            }
            break;

        default: sOSName = "OS Inconnu";
    }

    m_sOsName = sOSName;
}

void ComputerInfo::parseCPU()
{
    int CPUInfo[4] = {-1};
    unsigned   nExIds, i =  0;
    char CPUBrandString[0x40];

    __cpuid(CPUInfo, 0x80000000);
    nExIds = CPUInfo[0];
    for (i=0x80000000; i<=nExIds; ++i)
    {
        __cpuid(CPUInfo, i);

        if  (i == 0x80000002)
            memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo));
        else if  (i == 0x80000003)
            memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo));
        else if  (i == 0x80000004)
            memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo));
    }

    m_sCpuName = CPUBrandString;

    /************************************/
    char Buffer[_MAX_PATH];
    DWORD BufSize = _MAX_PATH;
    DWORD dwMHz = _MAX_PATH;
    HKEY hKey;

    long lError = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
            "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
             0,
             KEY_READ,
             &hKey);
    
    if(lError != ERROR_SUCCESS)
    {
           FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
                         NULL,
                         lError,
                         0,
                         Buffer,
                         _MAX_PATH,
                         0);
    }

    RegQueryValueEx(hKey, "~MHz", NULL, NULL, (LPBYTE) &dwMHz,
                    &BufSize);

    char string[512] = { 0 };
    
    sprintf(string, "%i", dwMHz);

    m_sCpuSpeed = std::string(string) + " MHz";
}

void ComputerInfo::parseMemory()
{
    MEMORYSTATUSEX status;
    
    status.dwLength = sizeof(status);

    GlobalMemoryStatusEx(&status);

    int totalPhysicalMem = 0;
    int availableMem = 0;

    totalPhysicalMem = (int) status.ullTotalPhys / 1024;
    totalPhysicalMem = (totalPhysicalMem / 1024) + 1;

    availableMem = (int) status.ullAvailPhys / 1024;
    availableMem = (availableMem / 1024) + 1;

    m_sTotalMemory = MiscManager::intToString(totalPhysicalMem) +
                   " MB";
    m_sAvailableMemory = MiscManager::intToString(availableMem) +
                       " MB";
}


Résumé :

Cette classe permet d’accéder aux informations de votre système d’exploitation Windows.

 

Macro avec multiple passage d’arguments

Intro :

Parfois il est nécessaire d’utiliser une macro avec nombre variable d’arguments / paramètres

#define fonction(param1, ...) printf(param, ##__VA_ARGS__);

Utilisation :

Ce sont des macros similaires aux fonctions qui contiennent un nombre variable d’arguments.

On peut utiliser une macro avec un nombre variables d’arguments :


ma_macro("test", "du_texte", "un_commentaire", "une_histoire");

Le mot-clé __VA_ARGS__  sert à remplacer les occurences de paramètres dans la fonction appelée.

Résumé :

Les macros avec nombre variable d’arguments peuvent toujours trouver leur utilité même s’il ne sont pas très utilisées.

Références :

https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html

Astuces pour bien coder en C++

C++-unofficial.sh-600x600

Intro :

Voici quelques astuces qui vous permettrons de mieux vous organiser dans l’étape d’écriture de votre code.

Safe Delete :

Pour détruire correctement vos objets instanciés, vous pouvez utiliser ces macros :


#define SAFE_DELETE(a) { delete (a); (a) = nullptr; }
#define SAFE_RELEASE(x) { x->Release();  (x) = nullptr; }

SAFE_DELETE(a);
SAFFE_RELEASE(x);


Fichier de déclaration de defines :

Utiliser un fichier include (Defines.h) où vous mettrez toutes vos déclarations de defines.
Par principe il vaut mieux  regrouper ensemble tout ce qui se ressemble.


#define FIRST_PERSON_CAMERA_SPEED 3.0f
#define OGRE_RENDER_SYSTEM_NAME "Direct3D9 Rendering Subsystem"
#define TIME_FADE_ANNOUNCE 2.0f
#define GAME_VERSION_REVISION "2323"
#define GAME_VERSION_MINOR "1"
#define GAME_VERSION_MAJOR "0"


Commentaires :

En général il vaut mieux bien nommer correctement les noms des variables et des fonctions
plutôt que de mettre des commentaires partout.

« Really good code comments itself » :

Sortie de texte pour débugger :

Sous Visual Studio on peut utiliser :

OutputDebugString(_T("text"));

Cela permettra au programme d’écriture sur la sortie debug de VC++.


En-tête :

Pour expliquer et situer le rôle d’un fichier source vous pouvez créer une en-tête dans chaque fichier :


//----------------------------------------------------
// Auteur : Clément Profit
// Nom du fichier : Console.cpp
// Date de création : Juillet 2014
// Description : Une console quake-like avec commandes et
// appels d'events
//----------------------------------------------------

Pour expliquer et situer le rôle d’une méthode :


//--------------------------------------------------
// EventManager::Trigger
//--------------------------------------------------

 

Principes :

1 – ne jamais arrêter son travail quand le programme ne fonctionne pas ou lorsqu’il ne compile pas. En effet lors de la reprise ultérieure de votre travail vous aurez pas de bug(s) à corriger que vous aurez oubliés de résoudre.

2 – pensez à abuser l’utilisation des macros lorsque votre code devient répétitif.

Macro prédéfinies :

Nom de la fonction courante :


#if defined(_MSC_VER)
    #define CURRENT_FUNCTION_NAME __FUNCTION__
#elif defined(__GNUC__) && (__GNUC__ >= 3 )
    #define CURRENT_FUNCTION_NAME __PRETTY_FUNCTION__
#else
    #define CURRENT_FUNCTION_NAME "Unknown function"
#endif

Pour le reste voir l’article Préprocesseur

Assertions :

Utiliser ces assertions un peu partout dans votre code.
L’instruction asm { int 3 } indiquera au débogueur un point d’arrêt près de l’assertion en cause.


#ifndef ASSERT_H
#define ASSERT_H

#ifndef NDEBUG

#define AssertMsg(expr, msg) if (!(expr)) { std::wout << (#expr << " " << __FILE__ << " " << __LINE__ << " " << CURRENT_FUNCTION_NAME << " " << msg << std::endl); __asm { int 3 } }
#define AssertNULLPointer(pointer) if (pointer == NULL) { AssertMsg(pointer, #pointer " == NULL"); __asm { int 3 } }
                    
#else //! non-debug

#define AssertMsg(expr, msg) fastprint(msg);
#define SKIP_IF_FAILED_AssertMsg(expr, msg) if (expr)
             
#endif
                                             
#endif

Notations :

Utiliser la notation hongroise pour noter vos variables et vos noms de fonctions :

g : utiliser pour les variables globales – g_counter
m : utiliser pour les variables membres – m_counter
p : utiliser pour les variables pointeurs – m_pActor
V : utiliser pour les fonctions virtuelles – VDraw()
I : utiliser pour les classes interfaces – class IDrawable

Exemple :


Ogre::OverlayManager* m_pOverlayManager;
Ogre::Overlay* m_pOverlayConsole;
Ogre::OverlayContainer* m_pPromptImage;

std::string m_sCurrentCommmandLine;
std::string m_sPrompt;

float m_fScrollYOffset;
float m_fHeight;
float m_fWidth;
float m_fCharWidth;

Les différents types de style de codage peuvent être source de conflit entre programmeurs.

L’important est d’être régulier dans la beauté de la syntaxe,
peu importe quel style / norme de codage vous employez.

Utiliser des raccourcis pour vos classes singletons :


#define EVENT_MANAGER EventManager::getSingletonPtr()


Macro fastprint(texte) :

Pour imprimer du texte ou une variable rapidement (la macro supporte du texte « wide string » c’est-à-dire supportant l’alphabet français contrairement à printf ou à std::cout).


#define fastprint(msg) std::wcout << #msg " = " << msg << std::endl;

 

Macro debugprint(texte) :

Pour déboguer vos applications on se sert de cette macro pour afficher facilement une variable

#define debugprint(msg) std::cout << msg << " = " << std::endl;

(todo mulitples paramètres)

 

Constructeur :

Ne jamais faire échouer l’initialisation d’un objet dans son constructeur.
Définissez une méthode bool Initialize() à la place (qui peut échouée quant-à-elle) que vous appelez juste après le « new » et qui renvoit un booléen indiquant si ou non l’initialisation de l’objet à échouée.

DXTrace :

Pour afficher éventuellement une erreur DirectX, on peut utiliser cette macro :

#if defined(DEBUG) | defined(_DEBUG)
    #ifndef HR
    #define HR(x)                                      \
    {                                                  \
        HRESULT hr = x;                                \
        if(FAILED(hr))                                 \
        {                                              \
            DXTrace(__FILE__, __LINE__, hr, #x, TRUE); \
        }                                              \
    }
    #endif

#else
    #ifndef HR
    #define HR(x) x;
    #endif
#endif

 

Const correctness  :

 

Faire une pause dans le programme :


#define WAIT_PROCESS int i; std::cin >> i; // Ne pas utiliser system("pause"); !

 

Utilisation de la fenêtre Outline de Visual Studio C++ :

Il est aisé de naviguer à travers les méthode de vos classe avec cette fenêtre.

outlines

 

Bien choisir la taille de ses types :

Utilisez uint16 à la place de uint32 quand cela est possible, cela réduira la taille consommée par votre programme à l’exécution. Vous pouvez continuer à utiliser uint32 lorsque que la variable est utilisée temporairement (par exemple dans les boucles), en effet il n’est pas important d’utiliser des variables de type uint16 sur la pile car elles sont détruites tout de suite après la portée, il faut utiliser des uint16 uniquement sur le tas.


// Après avoir ignorer un bloc de code on nous propose la reprise du programme en cours
#define SKIP_IF_FAILED_AssertMsg(expr, msg) if (!(expr)) \
FailedAssertMessageBox(#expr, __FILE__, __LINE__, CURRENT_FUNCTION_NAME, msg, true); \
else

Résumé :

Ces méthodes permettent de faciliter le codage de vos programme et de
rendre le code plus clair et plus joli.