dimanche 16 mai 2010

OpenSceneGraph - Les GUIEventHandler

Une (relative) nouveauté d'OpenSceneGraph est l'ajout d'un framework pour l'interface graphique. J'ai notamment trouvé que les GUIEventHandler étaient à la fois simples et très puissants, puisqu'ils permettent de réagir à tout un tas d'événements de la fenêtre graphique. L'exemple le plus simple est probablement celui-ci, qui est un handler calculant le nombre d'images par secondes. Lors de la création du handler, on lui passe un pointeur sur un flottant, lequel sera mis à jour à chaque rafraîchissement. Le calcul est trivial puisque l'on peut simplement réagir à l'événement "frame", et récupérer le nombre de secondes depuis que le programme tourne.


class FpsHandler : public osgGA::GUIEventHandler
{
public:
FpsHandler(const boost::shared_ptr<double> & fps):
m_fps(fps),
m_previousTime(0)
{
}

private:
bool handle(const osgGA::GUIEventAdapter & ea,
osgGA::GUIActionAdapter &)
{
switch(ea.getEventType())
{
case(osgGA::GUIEventAdapter::FRAME):
{
double currentTime = ea.getTime();
double diff = currentTime - m_previousTime;
if(diff > 0)
{
*m_fps = 1 / diff;
}
else
{
*m_fps = 0;
}
m_previousTime = currentTime;
break;
}
default:
break;
}

return false;
}

boost::shared_ptr<double> m_fps;
double m_previousTime;
};


Depuis mon programme, j'insère mon FpsHandler dans mon viewer, puis je créé un événement wxWidgets qui va lire ce temps toutes les secondes et l'afficher. Difficile de faire plus simple!

En bonus, ces handlers sont totalement indépendants du reste du code. Alors que j'ai déjà implémenté une multitude de calculateurs d'images par secondes dans les divers frameworks que j'utilise (SDL, Sfml, wxWidgets...), celui-ci peut simplement dormir dans une bibliothèque d'utilitaires OSG, et être utilisé dans n'importe quelle application.

Puisque les handlers s'ajoutent au viewer, il est ainsi possible de concevoir un grand nombre de composants résolvant chacun un problème donné, et de les combiner dans un programme.

Textures combinées

J'ai souvent eu des soucis à rendre mes textures de terrain jolies. Mon plus gros problème, généralement, est l'effet de répétition. Surtout avec les textures moches du genre de celles que je créé moi-même. Prenons par exemple cette simple texture d'herbe.



On applique la texture à ce terrain, et hop, l'effet de répétition prend toute sa mesure. C'est moche!



En même temps, si l'on réduit la répétition de la texture, c'est moche aussi, car la texture semble grossière et peu détaillée.



Mais si l'on calcule la moyenne des deux, avec un facteur qui évite la répétition, comme par exemple 16 et 3.44 (!), tout d'un coup, ça rend beaucoup mieux! Pas d'artefacts de répétition, mais la texture maintient l'impression de détail.



En plus, ce genre de calcul est trivial à effectuer avec le petit shader:


gl_FragColor =
diffuse
* (texture2D(grassTex, t * 16)
+ texture2D(grassTex, t * 3.44)) / 2;


L'on multiplie la couleur diffuse calculée à partir de l'éclairement par la moyenne des textures, en multipliant les coordonnées de la texture par nos facteurs.

jeudi 13 mai 2010

Les nouveautés dans le monde merveilleux du meuporg

Notons tout d'abord une nouvelle vidéo chez le projet Infinity, présentant les dernières avancées du moteur graphique. L'on observera donc le passage auprès d'une gigantesque station spatiale, la traversée d'un anneau, puis l'arrivée sur la planète, avant de remonter vers l'espace. Comme d'habitude, c'est absolument magnifique, et l'effet d'échelle est très impressionnant. Les sons ont été améliorés, notamment le bruit du vent en vue extérieure lors du passage dans l'atmosphère.

Le seul regret, c'est bien que la vidéo n'en montre pas plus! L'on verra un cockpit (inactif) et des reflets sur la verrière, mais pas plus d'informations en terme de gameplay. À quoi ressemblera donc une session?


La deuxième nouvelle d'importance est la libération de Ryzom! Non seulement le code, mais également toutes les données graphiques, sont maintenant open source. Le jeu en lui-même reste payant, mais rien d'empêche en théorie quiconque de créer son propre serveur.

Bien entendu, la différence pour le quidam moyen reste minime: faire tourner un serveur de MMORPG un peu serieux n'est pas possible sans sérieusement cracher au bassinet. De même, je ne suis pas sûr que beaucoup de gens iront se précipiter sur le code, lequel est probablement sacrément touffu. Néanmoins, c'est un grand pas dans la philosophie du libre, et je souhaite à l'équipe de Ryzom de démontrer qu'il est possible d'être à la fois ouvert et rentable!

dimanche 9 mai 2010

Variadic templates

Ça a été chaud, mais j'ai enfin réussi à faire quelque chose d'utile avec mes variadic templates. J'ai templatisé le code de base de données pour laisser le compilo générer les classes de transactions à ma place.

En utilisant le système de classes de transactions de pqxx, l'on se retrouve à écrire beaucoup de classes qui se ressemblent beaucoup. L'on a un constructeur dans lequel on passe ses paramètres d'entrée et de sortie, l'opérateur parenthèses qui effectue l'appel en lui-même. J'y rajoute généralement la préparation de ma requête. Un exemple parmi d'autres:


class CheckAccount : public pqxx::transactor<pqxx::transaction<> >
{
public:
CheckAccount(const boost::shared_ptr<Logger> & logger,
const std::string & login,
const std::string & password,
bool & success,
UserId & userId):
m_logger(logger),
m_login(login),
m_password(password),
m_success(success),
m_userId(userId)
{
}

void operator()(argument_type & T)
{
TimedLogger t(m_logger, "Executed \"" + getRequest() + "\"");
pqxx::result result = T.prepared(getPlan())(m_login)(m_password).exec();
if(result.size() == 1)
{
m_success = true;
m_userId = UserId(result.at(0).at(0).as<uint32_t>());
}
else
{
m_success = false;
}
}

static std::string getPlan()
{
return "checkAccount_plan";
}

static std::string getRequest()
{
return "select checkAccount($1, $2)";
}

static void prepare(const boost::shared_ptr<pqxx::connection> & dbConn)
{
dbConn->prepare(getPlan(), getRequest())
("varchar(255)", pqxx::prepare::treat_string)
("varchar(255)", pqxx::prepare::treat_string);
}

private:
boost::shared_ptr<Logger> m_logger;
std::string m_login;
std::string m_password;
bool & m_success;
UserId & m_userId;
};


Le problème, c'est que c'est très verbeux, et qu'en écrire 2 ou 3, ça passe encore, mais des dizaines, ça enlève tout le fun du code.

J'ai donc profité des variadic templates pour tenter de réduire un petit peu la chose. L'idée est de créer une classe policy, qui va contenir les informations importantes de la requête:


class CheckAccount
{
public:
static std::string getPlan()
{
return "checkAccount_plan";
}

static std::string getRequest()
{
return "select checkAccount($1, $2)";
}

typedef std::tuple<std::string, std::string> InputT;
typedef std::tuple<int> OutputT;
};


Cette classe étant définie, on l'utilise pour créer la classe requête attendue, et on la lance.


typedef Request<CheckAccount> CheckAccountRequest;
CheckAccountRequest::prepare(dbConn);
dbConn->perform(CheckAccountRequest(std::make_tuple(login, password), result));


La difficulté est de correctement gérer la préparation d'une part, et l'appel d'autre part. Voici à quoi ressemble la classe de requête:


template<class POLICY>
class Request : public pqxx::transactor<pqxx::transaction<> >
{
public:
Request(const typename POLICY::InputT & input,
typename POLICY::OutputT & output):
m_input(input),
m_output(output)
{
}

void operator()(argument_type & T)
{
pqxx::prepare::invocation invocation = T.prepared(POLICY::getPlan());
internals::call(invocation, m_input);
invocation.exec();
}

static void prepare(const boost::shared_ptr<pqxx::connection> & dbConn)
{
internals::prepare(dbConn,
POLICY::getPlan(),
POLICY::getRequest(),
typename POLICY::InputT());
}

private:
const typename POLICY::InputT & m_input;
typename POLICY::OutputT & m_output;
};


Toute la logique se trouve dans les fonctions templates "prepare" et "call". Regardons donc la préparation:


template<typename ... Args>
class Prepare;

template<typename T, typename ... Args>
class Prepare<T, Args...>
{
public:
void operator()(const pqxx::prepare::declaration & declaration)
{
Prepare<Args...>()(declaration
(SqlType<T>::getSqlType(),
SqlType<T>::getParamTreatment()));
}
};

template<>
class Prepare<>
{
public:
void operator()(const pqxx::prepare::declaration & declaration)
{
}
};

template<typename ... Args>
void prepare(const boost::shared_ptr<pqxx::connection> & dbConn,
const std::string & plan,
const std::string & request,
const std::tuple<Args...> & )
{
Prepare<Args...>()(dbConn->prepare(plan, request));
}


L'on appelle récursivement Prepare, en dépaquetant le template variadique au fur et à mesure. A chaque appel, l'on utilise une classe utilitaire SqlType qui renvoie le nom SQL et traitement à effectuer sur le paramètre en fonction du type passé (par exemple, std::string s'appelle text en SQL, et nécessite d'être échappé).

Le code déroule donc le tuple, et utilise le type de tête pour gérer la préparation.

Jetez un coup d'oeil dans le <a href="http://adh.svn.sourceforge.net/viewvc/adh/framework/trunk/utils/Db.h?view=markup">dépôt subversion</a> sur la manière de gérer la partie "call": plutôt que de dérouler le tuple, ma récursion se fait sur un paramètre template entier qui correspond à l'index de l'élément du tuple qui m'intéresse.

Malheureusement, C++0x est encore très expérimental, et cela se sent: il faut beaucoup tripoter pour que les choses marchent, et la bibliothèque standard est assez pauvre sur les nouveaux types (pas de "get_head" et "get_tail" pour std::tuple, par exemple).

Mais je ne regrette pas de m'y être mis. Je n'ai plus qu'à gérer les paramètres de sortie, et à moi les requêtes solides et typesafe en un clin d'œil!

vendredi 7 mai 2010

C++0x - Premiers pas

Aucun doute n'est plus permis, le x est un nombre hexadécimal!

Mais trêves de finasseries, j'ai fini par installer g++ 4.5 pour essayer un peu les nouvelles fonctionnalités. Je m'inquiétais un petit peu de devenir un dinosaure du C++ et de ressentir une méfiance instinctive, mais de fait j'ai plutôt envie de les utiliser le plus vite possible!

Ma fonctionnalité préférée est sans doute les lambdas, dont le plus simple exemple est probablement celui-là:


#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

int main()
{
std::vector<std::string> v;
v.push_back("a");
v.push_back("b");
v.push_back("c");
std::for_each(v.begin(),
v.end(),
[](const std::string & e) {std::cout << e << std::endl;});
return 0;

}

Bien sûr, cet exemple n'est pas vraiment moins verbeux qu'un BOOST_FOREACH ou que le nouveau ranged based for. Par contre, je m'attends à ce que cette fonctionnalité rende un std::accumulate beaucoup plus utile. Dans tous les cas, il sera agréable de ne pas à avoir à écrire de grosses boucles avec des itérateurs dans tous les sens.

Une autre fonctionnalité sympa pour la route, les variadic templates. Sans plus attendre, voyons un exemple:

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

void printElements()
{
}

template<typename T, typename... Args>
void printElements(const T & value, const Args&... args)
{
std::cout << value << std::endl;
printElements(args...);
}

int main()
{
printElements(std::string("abc"), 17, 25.14, std::string("def"));
return 0;
}

Le résultat est le suivant:

abc
17
25.14
def

Cet exemple créé une fonction qui peut recevoir n'importe quel nombre de paramètres de n'importe quel type, et les imprime. Elle fonctionne récursivement: le args... dans printElements sépare le premier argument des autres, et l'appel résout donc vers la même fonction template, avec une liste d'arguments variadiques réduite de 1. La récursion finit lorsque la liste d'arguments variadiques est vide, cas pour lequel c'est la fonction sans paramètres qui est appelée.

Je me demande si les templates variadiques pourraient résoudre mes problèmes de verbosité du code de base de données: pour le moment, je me retrouve à écrire une classe spécifique pour chaque appel, parce que les types d'entrée et de sortie sont particuliers à chaque classe. Avec les templates variadiques, il devrait être possible d'écrire quelque chose de générique... A creuser!

jeudi 6 mai 2010

Statistiques

Le framework vient juste d'atteindre 10 000 lignes! Voici les statistiques du code. Si la couche ORM (sur laquelle je vais avoir beaucoup de choses à poster) fonctionne correctement, attendez vous à un sacré saut vers le haut, parce que le code autour de la base de données est particulièrement verbeux.


SLOC Directory SLOC-by-Language (Sorted)
3005 utils cpp=3005
813 messages cpp=772,xml=41
746 geom cpp=746
742 unittests cpp=742
533 osgutils cpp=533
532 libweb ml=532
508 wxutils cpp=508
471 server cpp=471
413 sound cpp=413
377 openrail cpp=377
288 orm ml=188,xml=100
237 dsgen ml=237
235 wxosg cpp=235
229 rail cpp=229
202 logserver cpp=202
158 chatserver cpp=158
157 logapi cpp=157
108 doc perl=108
84 wxdemo cpp=84
73 wxosgdemo cpp=73
47 logviewer cpp=47
43 listviewer ml=43
0 authserver (none)
0 bin (none)
0 data (none)
0 lib (none)
0 listserver (none)
0 top_dir (none)


Totals grouped by language (dominant language first):
cpp: 8752 (87.51%)
ml: 1000 (10.00%)
xml: 141 (1.41%)
perl: 108 (1.08%)




Total Physical Source Lines of Code (SLOC) = 10,001
Development Effort Estimate, Person-Years (Person-Months) = 2.24 (26.93)
(Basic COCOMO model, Person-Months = 2.4 * (KSLOC**1.05))
Schedule Estimate, Years (Months) = 0.73 (8.74)
(Basic COCOMO model, Months = 2.5 * (person-months**0.38))
Estimated Average Number of Developers (Effort/Schedule) = 3.08
Total Estimated Cost to Develop = $ 303,171
(average salary = $56,286/year, overhead = 2.40).
SLOCCount, Copyright (C) 2001-2004 David A. Wheeler
SLOCCount is Open Source Software/Free Software, licensed under the GNU GPL.
SLOCCount comes with ABSOLUTELY NO WARRANTY, and you are welcome to
redistribute it under certain conditions as specified by the GNU GPL license;
see the documentation for details.
Please credit this data as "generated using David A. Wheeler's 'SLOCCount'."

dimanche 2 mai 2010

Blender et radiosité

Évidemment, il va falloir penser à faire quelque chose d'un peu plus original que la bonne vielle boite de Cornell, mais c'est un bon entraînement.

samedi 1 mai 2010

Fglrx sur Squeeze

Depuis combien de temps étais-ce dans les bacs, je ne sais trop, mais j'ai essayé aujourd'hui un petit aptitude install fglrx-driver, et magie, les drivers 3D sont de retour! Après plusieurs mois, cela fait plaisir d'y revenir. Je vais pouvoir me remettre à la programmation 3D, et en attendant je suis de retour à (me faire) fragger sur Urban Terror. On ne se refait pas!