Archives pour la catégorie Algorithmes

Utilisation de la librairie Zlib pour gérer des flux de compression

zlib3d-b1

Intro :

Il est très utile de pouvoir compresser des données en mémoire ou des fichiers sur disque ou mémoire de masse. Pour cela, on va utiliser la fameuse librairie C open-source nommée Zlib !

Prérequis :

– Savoir lire du C ou du C++

– Savoir configurer l’importation d’une librairie sous Visual C++

Explications :

Elle implémente l’algorithme de compression deflate et peut créer des fichiers au format gzip.
La bibliothèque est aussi multi-plateforme.

Résumé :

Nous avons présenté une classe C++ encapsulant les fonctions de compression de la librairie Zlib ; cette classe permet de compresser des fichiers ou des données en mémoire.

Références :

– http://www.zlib.net/manual.html

– http://www.zlib.net/zlib_how.html

– http://www.zlib.net/zlib_faq.html

Apprendre à utiliser TinyXML pour charger vos fichiers XML

tinyxml

Intro :

Parfois dans un programme ou un jeu vidéo, il est nécessaire de charger des données sous format XML. Par exemple pour un éditeur de scène / monde, on peut vouloir charger les positions des entités directement depuis un fichier Scene.xml par exemple.

Prérequis :

– Savoir lire du C++

– Savoir lire un fichier XML

– Savoir importer des fichiers sources dans Visual C++

Explications :

TinyXML est une toute petite librairie C++ permettant de charger et d’analyser les données stockées dans un fichier XML. Elle est sous la licence Zlib (cela signifie que vous pouvez l’utiliser pour n’importe quoi !)

D’abord il vous faut télécharger les sources de TinyXML. La dernière version à la date d’écriture de cet article est la version 2.

Deux choix s’offrent à vous : soit d’utiliser la version 1 ; soit d’utiliser la version 2.

Les avantages de choisir la 2ème : meilleur gestion de la mémoire ; lecture plus rapide du fichier XML ; pas d’utilisation de la STL.

TinyXML charge complètement un fichier XML avec la méthode LoadFile()… Il vous reste ensuite le soin d’analyser votre fichier XML en fonction de son contenu en terme de nœuds.

On précise que XML est l’abréviation de « eXtensible Markup Language ». XML est un format pour stocker des données, pas un langage de programmation !
Celles-ci sont écrites entre des balises ou sous forme d’attributs, et l’ensemble est écrit sous forme d’un arbre.

Voici la structure d’un fichier XML :

<?xml version="1.0" ?>
<root>
    <UnElement attribut1 ="une valeur" />
    <UnAutreElement attribut2 = "2" attribute3 = "3">
        <Element3 attribut4="4" />
        Du Texte !
    </UnAutreElement>
</root>

 

Il vous faut importer dans votre projet Visual C++ la librairie directement par l’intermédiaire de ces fichiers :

– tinystr.cpp
– tinyxmlerror.cpp
– tinystr.h
– tinyxml.h
– tinyxml.cpp
– tinyxmlparser.cpp

 

Dans votre fichier source .cpp utilisant TinyXML, il vous faudra écrire #include « tinyxml.h » en tant que fichier d’en-tête !

Vous pouvez définir le define #define TIXML_USE_STL pour activer l’utilisation de la STL dans TinyXml.

Sachez que TinyXML reconnaît automatiquement l’encodage utilisé par le fichier à charger (il n’est pas nécessaire de renseigner l’attribut « encoding »).

Commençons l’analyse d’un fichier quelconque :

TiXmlDocument doc;
if(!doc.LoadFile("Test.xml"))
{
    std::cerr << doc.ErrorDesc() << std::endl;
    return FAILURE;
}

 

Le fichier « doc » contient toutes les données d’analyse du fichier XML chargé.

TiXmlElement* pRoot = doc.FirstChildElement();
if (pRoot == nullptr)
{
    std::cerr << "Echec chargement du fichier. Aucun élément racine." << std::endl;
    doc.Clear();

    return FAILURE;
}

 

La méthode FirstChildElement() renvoie un pointeur vers un objet TinyXmlElement qui représente le premier nœud du fichier XML en question. FirstChildElement() prend un paramètre une chaîne de caractères représentant le nom du nœud en question.

Une boucle permet d’afficher tout les noms des éléments du fichier :

for (TiXmlElement* pElem = pRoot->FirstChildElement(); pElem != nullptr; pElem = pElem->NextSiblingElement())
{
    std::string elemName = pElem->Value();
}

 

Le principe est le suivant : on parcourt chaque nœud en extrayant de chaque nœud enfant les attributs qui nous intéressent !

...

[...La boucle...]

    const char* attr = nullptr;

    if (elemName == "UnElement")
    {
        attr = elem->Attribute("attribut1");

        if (attr != nullptr)
        {
            // Faire le travail d'analyse ici
        }
    }

[...Fin de boucle...]

 

La variable attr représente la valeur de l’attribut en question. Si l’attribut ne comporte pas le nom spécifié. La méthode Attribute(…) renvoie nullptr.

On peut tester si le contenu d’un nœud est du texte avec la méthode elem->ToText(). Elle renvoie nullptr s’il n’y a pas de texte.

Il peut être utile d’utiliser une structure de données spécifique customisée afin de stocker les données lues depuis le fichier XML. Mais cela reste facultatif !

On fini l’analyse avec l’appel de Clear() : doc.Clear();

Pour faciliter le parcours des nœuds, on peut utiliser ce qu’on appelle des handles.
Exemple :

TiXmlHandle hdl(&doc);
TiXmlElement *elem = hdl.FirstChildElement().FirstChildElement().Element();

 

La méthode QueryIntAttribute(…) permet d’obtenir la valeur d’un attribut sous forme de integer

Voici la liste des classes de TinyXML formatée par Doxygen : http://www.grinninglizard.com/tinyxmldocs/annotated.html

Résumé :

Simple et efficace, la petite librairie TinyXML facilitera vos chargement de données sous format XML.

Références :

– http://www.dinomage.com/2012/01/tutorial-using-tinyxml-part-1/

Comment calculer la matrice de rotation d’un Billboard

tag

Intro :

Qu’est-ce un billboard ? Un billboard est un objet 3D (en général d’apparence 2D) qui fait toujours face à la caméra.

Prérequis :

– Savoir ce qu’est une matrice

– Savoir lire une matrice

– Savoir en quoi consiste la matrice de vue

Explications :

Rappelez vous que la matrice de vue (celle de la caméra) est définie ainsi :

viewmatrix

En effet, cette matrice est construite à partir de trois vecteurs :

uprightlook

Avec une origine (ainsi nommée « Caméra ») représentant la position de celle-ci par rapport au repère commun à tous les nœuds de la scène, cette dernière concerne la matrice World (matrice de monde)

Une fois construite, la matrice de vue est une matrice qui doit être mise à jour constamment dans la boucle de rendu.

Décomposition de la matrice View :

\vec{LookAt}_x = \textbf{View}_{02}
\vec{LookAt}_y = \textbf{View}_{12}
\vec{LookAt}_y = \textbf{View}_{22}

\vec{Right}_x = \textbf{View}_{00}
\vec{Right}_y = \textbf{View}_{10}
\vec{Right}_y = \textbf{View}_{20}

\vec{Up}_x = \textbf{View}_{01}
\vec{Up}_y = \textbf{View}_{11}
\vec{Up}_y = \textbf{View}_{21}

 

On normalise ensuite ces trois derniers vecteurs :

{\vec{LookAt}}_{norm}=\frac{{\vec{LookAt}}}{\|{\vec{LookAt}}\|}
{\vec{Right}}_{norm}=\frac{{\vec{Right}}}{\|{\vec{Right}}\|}
{\vec{Up}}_{norm}=\frac{{\vec{Up}}}{\|{\vec{Up}}\|}

 

A noter que l’on peut retrouver un vecteur connaissant les deux autres. Par exemple, l’on veut retrouver le vecteur {\vec{Up}} connaissant {\vec{Right}} et {\vec{LookAt}} . On procède ainsi  :

{\vec{Up}} = {\vec{LookAt}}\times{\vec{Right}}

 

Maintenant pour construire notre Billboard il nous faudra créer une matrice de rotation \textbf{R} . C’est avec cette matrice de rotation que seront multipliés toutes les vertices du Billboard de façon à ce qu’ils soient toujours en face de la caméra !

On calcule la matrice \textbf{R} de la façon suivante :

\textbf{R}_{0*} = {\vec{Right}}
\textbf{R}_{1*} = {\vec{Up}}
\textbf{R}_{2*} = {\vec{LookAt}}

 

C’est-à-dire de manière plus détaillée :

\textbf{R}_{01} = \vec{Right_x}
\textbf{R}_{02} = \vec{Right_y}
\textbf{R}_{03} = \vec{Right_z}

\textbf{R}_{11} = \vec{Up_x}
\textbf{R}_{12} = \vec{Up_y}
\textbf{R}_{13} = \vec{Up_z}

\textbf{R}_{21} = \vec{LookAt_x}
\textbf{R}_{22} = \vec{LookAt_y}
\textbf{R}_{23} = \vec{LookAt_z}

 

Voici le Vertex Shader qui applique le calcul en question :

PS_INPUT VS(VS_INPUT input)
{
    PS_INPUT output = (PS_INPUT)0;
    
    // Extraction du LookAt vector
    float3 look;
    look.x = View[0][2];
    look.y = View[1][2];
    look.z = View[2][2];
    look = normalize(look);    

    // Extraction du Right vector
    float3 right;
    right.x = View[0][0];
    right.y = View[1][0];
    right.z = View[2][0];
    right = normalize(right);
    
    // Détermination du Up vector
    float3 up = cross(look, right);
    up = normalize(up);

    // Matrice de rotation qui fait face à la caméra
    float3x3 R;
    R[0] = right;
    R[1] = up;
    R[2] = look;
    
    float3 posW = mul(input.Pos, R);

    output.Pos = mul( float4(posW, 1.0f), World );
    output.Pos = mul( output.Pos, View );
    output.Pos = mul( output.Pos, Projection );
    
    output.Color = input.Color;    
    output.Page = input.Page;    
    output.Tex = input.Tex;        
    
    return output;
}

 

Voici un Billboard affiché qui représente une quête disponible :

 

billboard

 

Résumé :

Nous avons expliqué comment créer un Billboard en construisant une matrice de rotation spéciale qui tourne et qui fait toujours face à la caméra.

Références :

– Des sujets issus de quelques forums Internet comme celui de www.gamedev.net

Une classe DelayedTimerManager pour gérer les délais de temporisation

unnamed

Intro :

Parfois nous avons besoin, dans le code, d’appeler des fonctions / méthodes à un certain intervalle de temps !

Prérequis :

– Savoir lire du C++

Explications :

Voici le fichier DelayedTimerManager.h :

#ifndef DELAYED_TIMER_MANAGER_H
#define DELAYED_TIMER_MANAGER_H

#include "Singleton.h"

#include <map>

class DelayedTimerManager : public Singleton<DelayedTimerManager>
{
public:
    struct TimerData
    {
        TimerData() : iElapsedTime(0), iLastTime(0), iWaitingTime(0)
        {
        }

        uint64 iElapsedTime;
        uint64 iLastTime;
        uint32 iWaitingTime;
    };

    DelayedTimerManager();
    virtual ~DelayedTimerManager();

    bool TimerWait(const std::string& sTimerName);

    void RegisterTimer(const std::string& sTimerName, uint32 iWaitingTime);
    void UpdateTimer(const std::string& sTimerName, uint32 iWaitingTime);

    void ResetTimer(const std::string& sTimerName);

private:
    std::map<std::string, TimerData> m_timers;
};

#endif

 

Voici le contenu du fichier DelayedTimerManager.cpp :

#include "DelayedTimerManager.h"

#include <string>
#include <Windows.h>

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

DelayedTimerManager::DelayedTimerManager()
{
}

DelayedTimerManager::~DelayedTimerManager()
{
}

bool DelayedTimerManager::TimerWait(const std::string& sTimerName)
{
    TimerData* pTimerData = &m_timers[sTimerName];

    uint32 iElapsedTime = GetTickCount() - pTimerData->iLastTime;
    uint32 iWaitingTime = pTimerData->iWaitingTime;

    if (iElapsedTime > iWaitingTime)
    {
        pTimerData->iLastTime = GetTickCount();    

        return true;
    }

    return false;
}

void DelayedTimerManager::RegisterTimer(const std::string& sTimerName, uint32 iWaitingTime)
{
    TimerData timerData;
    timerData.iWaitingTime = iWaitingTime;

    m_timers[sTimerName] = timerData;
}

void DelayedTimerManager::ResetTimer(const std::string& sTimerName)
{
    TimerData* pTimerData = &m_timers[sTimerName];

    pTimerData->iLastTime = 0;
}

void DelayedTimerManager::UpdateTimer(const std::string& sTimerName, uint32 iWaitingTime)
{
    TimerData* pTimerData = &m_timers[sTimerName];

    pTimerData->iWaitingTime = iWaitingTime;
}

 

Résumé :

Nous avons présenter le code pour gérer un système de temporisation.

La technique du back-face culling

backface

Intro :

La technique du back-face culling permet d’ignorer le rendu des triangles qui ne sont pas visible par l’observateur ou la caméra.

Prérequis :

–  Savoir ce qu’est un produit scalaire

Explications :

Par exemple, les triangles de l’intérieur d’un modèle ou personnage ne seront pas rendu si l’on rentre et se positionne dedans !

Bacfaceculling

Dans DirectX, les vertices d’un triangle sont définis dans un certain ordre (il existe deux sens : le sens « counter clockwise » et le sens « closewise»). Cet ordre va définir dans quel sens est orienté la normale du triangle en question (voir l’article sur la normale).

clock_wise

Comment ça fonctionne ?

Soit le vecteur représentant la position de la caméra \vec{c} ; et le vecteur représentant la position du centre du polygone à tester \vec{p} .

On calcule donc le vecteur \vec{v} = \vec{p} - \vec{c}

On a aussi le vecteur normal à ce même polygone \vec{n}

Mathématiquement :

k = \vec{v} \cdot \vec{n}

 

Si le produit scalaire k est positif (en fait cela dépend du type de culling) alors cela signifie que le triangle est orienté dans le même sens que la vue (il nous tourne le dos), et il peut donc être éliminé du rendu.

Voilà c’est tout simple !

Il faut savoir aussi que cet algorithme est géré automatiquement par DirectX et la carte graphique.

On peut le configurer comme suit :

D3D10_RASTERIZER_DESC rasterizerState;

rasterizerState.CullMode = D3D10_CULL_NONE;
rasterizerState.FillMode = D3D10_FILL_SOLID;
rasterizerState.FrontCounterClockwise = true;

m_pd3dDevice->CreateRasterizerState(&rasterizerState, &m_pRasterizerState);    
m_pd3dDevice->RSSetState(m_pRasterizerState);

 

Résumé :

Pour conclure, le backface culling est l’élimination des faces qui « tournent le dos » à la caméra et que l’on ne voit donc pas.

Références :

– http://jeux.developpez.com/faq/3d/?page=culling

– http://stackoverflow.com/questions/3595087/opengl-newbie-question-what-is-back-face-culling

– http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/3d-backface-culling-r1088

– https://en.wikipedia.org/wiki/Back-face_culling

– https://fr.wikipedia.org/wiki/D%C3%A9termination_des_surfaces_cach%C3%A9es

Générer une sphère MeshSceneNode

Sphere

Intro :

Il peut être utile de savoir générer une sphère à la volée par la définition de ses vertices et de ses indices.

Prérequis :

– Savoir lire du C++

– Savoir initialiser DirectX 10

– Savoir utiliser et comprendre un peu de trigonométrie

– Savoir utiliser la classe Renderable

– Savoir utiliser la classe MeshSceneNode

– Savoir utiliser la classe ShaderTechnique

Explications :

Voici l’implémentation de la classe shader :

//------------------------------------------------------
// Simple shader avec texturage
//------------------------------------------------------
class ShaderTechnique_BasicTexture : public ShaderTechnique
{
public:
    ShaderTechnique_BasicTexture(const std::string& sTextureFileName);
    virtual ~ShaderTechnique_BasicTexture();

    virtual bool Initialize();

    virtual void SetupShaderVariables();

    virtual void Update(float fTimeSinceLastFrame);

private:
    std::string m_sTextureFileName;
};

 

Voici le fichier ShaderTechnique_BasicTexture.cpp :

#include "ShaderTechnique_Declarations.h"

ShaderTechnique_BasicTexture::ShaderTechnique_BasicTexture(const std::string& sTextureFileName) :
ShaderTechnique("SimpleTextureEffect.fx", "Render", VertexLayoutType::PTN_VERTEX),
m_sTextureFileName(sTextureFileName)
{
}

ShaderTechnique_BasicTexture::~ShaderTechnique_BasicTexture()
{
}

bool ShaderTechnique_BasicTexture::Initialize()
{
    if (ShaderTechnique::Initialize() == false)
    {
        return false;
    }

    SetupShaderVariables();

    return true;
}

void ShaderTechnique_BasicTexture::SetupShaderVariables()
{
    RegisterMatrixVariable("World", ShaderVariableType::WORLD);
    RegisterMatrixVariable("View", ShaderVariableType::VIEW);
    RegisterMatrixVariable("Projection", ShaderVariableType::PROJECTION);

    RegisterTextureVariable("txDiffuse");

    AddTexture(m_sTextureFileName.c_str());

    SetTexture("txDiffuse", 0);
}

void ShaderTechnique_BasicTexture::Update(float fTimeSinceLastFrame)
{
    // On fait tourner la sphère / planète
    D3DXMATRIX mat;
    static float r = 0.0f;

    r += fTimeSinceLastFrame * 0.3f;

    D3DXMatrixRotationY(&mat, r);

    SetMatrix("World", &mat);

    SetAutoMatrix(ShaderVariableType::VIEW);
    SetAutoMatrix(ShaderVariableType::PROJECTION);
}

 

Voici le fichier SphereMeshSceneNode.h :

#ifndef SPHERE_MESH_SCENE_NODE_H
#define SPHERE_MESH_SCENE_NODE_H

#include "MeshSceneNode.h"
#include "Types.h"

class SphereMeshSceneNode : public MeshSceneNode
{
public:
    SphereMeshSceneNode();
    virtual ~SphereMeshSceneNode();

    void CreateSphere(uint16 iRings, uint16 iSectors, uint16 iRadius);

private:

};

#endif

 

Voici le fichier SphereMeshSceneNode.cpp :

#include <vector>

#include "SphereMeshSceneNode.h"
#include "Types.h"
#include "ShaderTechnique_Declarations.h"

SphereMeshSceneNode::SphereMeshSceneNode()
{
    // Chercher sur Google Image d'autres textures sphériques
    SetShaderTechnique( new ShaderTechnique_BasicTexture("earth.jpg") );
    CreateSphere(40, 40, 4);
}

SphereMeshSceneNode::~SphereMeshSceneNode()
{
}

// Voilà la méthode qui créer la sphère
void SphereMeshSceneNode::CreateSphere(uint16 iRings, uint16 iSectors, uint16 iRadius)
{
    std::vector<PTNVertex> vertices;
    std::vector<uint16> indices;
    
    float R = 1.f/(float)(iRings-1);
    float S = 1.f/(float)(iSectors-1);

    for (uint32 r = 0; r < iRings; r++)
    {
        for (uint32 s = 0; s < iSectors; s++)
        {
#define MATH_PI 3.141592653589793f
#define MATH_PI_2 MATH_PI / 2
            float const y = sin( -MATH_PI_2 + MATH_PI * r * R );
            float const x = cos(2*MATH_PI * s * S) * sin( MATH_PI * r * R );
            float const z = sin(2*MATH_PI * s * S) * sin( MATH_PI * r * R );

            PTNVertex v;

            v.position.x = x * iRadius;
            v.position.y = y * iRadius;
            v.position.z = z * iRadius;

            v.texture.x = 1 - s*S;
            v.texture.y = r*R;

            v.normal.x = -x;
            v.normal.y = -y;
            v.normal.z = -z;

            vertices.push_back(v);
        }
    }

    for (uint16 y = 0; y < iRings - 1; y++)
    {
        for (uint16 x = 0; x < iSectors - 1; x++)
        {
            indices.push_back( ((y + 0) * iSectors + x) );
            indices.push_back( ((y + 1) * iSectors + x) );
            indices.push_back( ((y + 1) * iSectors + x + 1) );
 
            indices.push_back( ((y + 1) * iSectors + x + 1) );
            indices.push_back( ((y + 0) * iSectors + x + 1) );
            indices.push_back( ((y + 0) * iSectors + x) );
        }
    }
    
    Build(vertices, indices);
}

 

Résumé :

Nous avons appris comment générer une sphère en 3D avec ses coordonnées de texture et normales.

Référénces :

– http://stackoverflow.com/questions/5988686/creating-a-3d-sphere-in-opengl-using-visual-c