lundi 21 juin 2010

Des grattes-ciel



Voilà 50 grattes-ciel sur mon terrain. Les modèles ont été faits sous Blender, et ont environ 5600 faces chacun, ce qui est beaucoup! Au rendu, OSG ne s'en sort pas trop mal, puisqu'il m'en affiche une bonne partie tout en restant au dessus des 100 images par seconde.

C'est typiquement le genre de situation qui va nécessiter des niveaux de détails! A très courte distance, le bâtiment est détaillé juste comme il faut. Mais de loin, il va falloir sérieusement réduire le nombre de faces. La solution que je compte suivre est de créer une version peu détaillée du bâtiment (pas beaucoup plus qu'un bête cylindre!), et de "cuire" les normales du modèle détaillé vers une texture pour le modèle peu détaillé. L'on peu ainsi espérer un rendu à peu près équivalent à distance, avec peut-être une centaine de polygones, au lieu de plusieurs milliers.

Au passage, les défauts de rendu liés au crénelage sont tristement visibles. Je compte pour le moment attendre que wxWidgets fournisse le support pour l'anti-crénelage, et si j'en ai besoin plus tôt, j'insérerai une surface SDL ou SFML pour faire le boulot.

samedi 19 juin 2010

Ça fait beaucoup de flèches



C'est 100.000 flèches qu'OpenSceneGraph m'affiche. Bon, d'accord, ça rame :) L'ensemble de l'exercice était plutôt une tentative pour pousser le moteur dans ses retranchements, et s'il me fallait vraiment en afficher autant, j'aurais recours à d'autres méthodes (imposteurs?).

C++0x - Default et delete explicites

Ce n'est pas vraiment une révolution, mais c'est une fonctionnalité plutôt agréable de C++0x: la possibilité de spécifier explicitement ses constructeurs par défaut, ou leur absence.

La syntaxe est des plus simples, voyez donc mon entity factory:


class EntityFactory
{
public:
EntityFactory() = default;
EntityFactory(const EntityFactory &) = delete;
EntityFactory & operator=(const EntityFactory &) = delete;
virtual ~EntityFactory() = default;
};

Cette signature indique que le constructeur par défaut "est" le constructeur par défaut, qu'il n'y a pas de constructeur par copie, pas d'assignement, et enfin que le destructeur par défaut "est" le destructeur par défaut.

La suppression du constructeur par copie et de l'assignement se faisaient généralement soit manuellement, en les déclarant privés (ce qui prenait un poil plus de place), ou en dérivant de la bonne vieille boost::noncopyable, ce qui prenait plutôt moins de place. La méthode "A la mano" est à mon avis la moins bonne, puisque la moins explicite. Entre boost et le delete C++0x, mon cœur balance, il faudra donc voir à l'usage.

Par contre, la possibilité de définir le destructeur virtuel par défaut est vraiment bien. et évite d'avoir à se farcir un corps de méthode vide.

Attention cependant! La règle qui dit que définir un constructeur supprime le constructeur par défaut implicite est encore valide. Ansi, ce petit programme:

class A
{
public:
A(const A &) = delete;
A & operator=(const A &) = default;
};

int main()
{
A a;
return 0;
}

ne fonctionnera pas comme prévu, et jettera le message "error: no matching function for call to ‘A::A()’". En effet, définir ne serais-ce que l'absence du constructeur par copie supprime le constructeur par défaut implicite.

Le programme correct est donc:

class A
{
public:
A() = default;
A(const A &) = delete;
A & operator=(const A &) = default;
};

int main()
{
A a;
return 0;
}

lundi 14 juin 2010

Et encore des en-têtes gcc précompilées

J'étais plutôt satisfait de mes gch, mes fichiers d'en-têtes pré compilées, mais au fur et à mesure que le code devient plus gros, et avec l'ajout de la compilation optimisée, je me heurtais de plus en plus souvent à la vitesse de mon disque.

En effet, ces fichiers d'en tête pré compilés, les fameux gch, prennent une place éléphantesque, surtout si on génère les informations de debug. Une en-tête contenant quelques fichiers STL et Boost pourra monter facilement à 100 megs. Si l'on rajoute par exemple wxwidgets pour la GUI, Open Scene Graph pour la 3D, OpenAL pour le son, on dépassera facilement les 300 megs.

Ayant initialement créé un fichier pré compilé pour chaque bibliothèque et application, la plus grande partie du build était passée à charger ces fichier gigantesques en mémoire. Une fois que les fichiers y sont, la compilation est rapide, car ils restent en cache pour la suite. Mais la bibliothèque suivante utilisant un autre ficher, l'on se retrouve à relire des centaines de megs. Au total, 15 bibliothèques et 2 builds (debug et optimisé) plus tard, c'étaient plusieurs gigs à chaque fois, et mon CPU restait désespérément à plat pendant que le disque turbinait.

La solution, relativement évidente une fois que le problème est clair, est de n'utiliser qu'un seul fichier d'en tête pour tout le monde. Sauf que je ne veux pas que le serveur, par exemple, réclame de se lier aux bibliothèques de son, juste parce qu'il référence le son via l'en-tête partagée.

J'ai donc créé un nouveau projet "gch", lequel contient 3 fichiers d'en-tête: Basic.h, Client.h et Server.h, lesquels contiennent les inclusions nécessaires aux trois types d'applications. Je n'ai plus qu'à référencer le bon fichier depuis chaque bibliothèque / exécutable, et la différence est tout à fait remarquable: mon CPU est maintenant à fond, et je profite pleinement de la compilation parallèle. Quelques tests confirment qu'une compilation prend maintenant 2 minutes et atteint les 3.5 gigs de RAM, contre 5 minutes et seulement 2.5 gigs de RAM sans la pré-compilation.

samedi 5 juin 2010

C'est comme si c'était fait!

C'est le truc avec les abstractions: quand elles sont bien faites, tout se met à marcher tout seul. Voilà donc un rail qui ressemble enfin à un rail.



(Même si la musique de Michel Legrand sur "The Go-Between", reprise dans l'émission "Faites entrer l'accusé", a beaucoup aidé).

Sniffe moi ce rail!

C'est très moche, mais les principes fondamentaux sont là: je peux afficher mon rail. Je suis satisfait de l'abstraction autour de mes sections: une section abstraite renvoie sa matrice de transformation pour une longueur donnée, et j'en dérive les sections droites et courbes. C'est là que mes vieux souvenirs sur les matrices de changement de base se sont rendus utiles!

Je peux donc diviser ma section abstraite et appliquer la transformation en chacun des points que j'ai choisi à la traverse, et au profil de mon rail. J'enfile ensuite les profils, et créé la boite correspondante.



Une fois que le rail aura la bonne taille, et tournera gentiment sur ses traverses, il va falloir trouver une texture qui ne soit pas trop moche, et tripatouiller les reflets pour obtenir un effet métallique.

jeudi 3 juin 2010

Courbes

Enfin, mes traverses suivent une voie courbée. Magique!

Le premier essai était plutôt bizarre:



Mais après quelques inversions aléatoires de produits vectoriels, les traverses s'alignent beaucoup plus gentiment:



C'est pas tout, mais maintenant, il va falloir y mettre des rails!