dimanche 22 mars 2009

Sélection

Voilà, l'étape suivante est achevée: l'on peut maintenant sélectionner les objets du monde, qui s'en retrouvent entourés d'une belle boite jaune. C'est la première étape vers l'interaction avec le monde: une fois que le système de sélection sera pleinement opérationnel, il sera trivial alors de récupérer la liste des actions possibles sur cet objet, ce qui nous approche d'autant des possibilités d'aller enfin se battre!

vendredi 20 mars 2009

Bump mapping, argh!

Je ne dois pas être loin, mais là, je n'y arrive vraiment pas... Saleté de bump mapping!



J'ai fini par comprendre qu'il fallait générer le tangent space pour arriver quelque part, mais c'est toujours pas ça.

dimanche 15 mars 2009

Les canons de Navarone

Les captures ne sont pas vraiment plus impressionnantes que les précédentes, mais ce n'est pas (seulement) pour montrer mes gros canons que je poste cette vue extérieure et cette vue depuis l'intérieur de la bête (Au lieu du disque bleu, imaginez quelques nuages et un homme oiseau!).





Non, ces captures montrent qu'enfin, la définition des zones est fonctionnelle: la liste des éléments graphiques de la zone est définie dans la base de données, et l'information envoyée au client lorsqu'il se connecte.



Mon cache des entités s'assure que le chargement des modèles est efficace, et que l'on ne duplique pas les éléments en mémoire.

Maintenant, j'avoue qu'aller bidouiller une table dans la base de données pour créer l'univers de jeu n'est pas particulièrement ergonomique, mais pour l'instant, on s'en contentera.

samedi 14 mars 2009

Nuages réfléchis dans l'eau



Encore un effet basé sur la déformation d'une texture. Maintenant que j'ai suffisamment bidouillé sur les effets simples, il est temps de passer aux choses sérieuses, j'ai nommé: la réflexion dans la flotte!

C'est un effet qui accumule les passes:
- Créer le stencil buffer pour le miroir
- Dessiner la scène normale, en mettant à jour le stencil pour éviter de dégommer plus tard ce que l'on dessine maintenant
- Réinitialiser le depth-buffer au niveau du miroir
- Dessiner la scène dans le miroir, dans un framebuffer
- Afficher le framebuffer comme une texture, en l'ayant déformé à l'aide d'un shader

C'est schématiquement l'association des démos osgreflect, osgprerender, et de mes déformations.

Plus qu'à s'y mettre!

Un outil pour générer tous mes messages

Comme vous le savez peut-être, réécrire tout le temps (presque) le même code m'ennuie à n'en plus finir. Or, à chaque fois qu'il me fallait écrire la petite classe qui représente un paquet d'informations à passer entre le client et le serveur, je me retrouve à écrire presque la même chose! Quelques inclusions, un constructeur par défaut, un constructeur qui prend toutes mes données, un empaqueteur de données et son inverse, et quelques getters.

Une solution possible aurait été d'utiliser de grandes maps de boost::variant. Pourquoi ne pas l'avoir fait, moi, le chantre du code dynamique? Peut-être l'envie d'avoir de bons getters, et de ne pas s'embêter avec tous les types qu'il aurait fallu mettre dans le variant. Bref, c'est fait.

Je me suis donc décidé à prendre exemple sur le wsdl, et écrire un petit générateur de code pour me décharger de toute cette ennuyeuse paperasse, à partir d'un fichier de description de l'interface.

Ce qui fut dit fut fait, et je suis maintenant l'heureux auteur d'un petit programme en Ocaml tout bête mais qui va me permettre de me concentrer sur le contenu plutôt que de m'embêter avec la syntaxe.

Passons par exemple la définition suivante:


<messages>
<message name="PingMsg">
<item name="data" type="std::string"/>
</message>
<message name="RadioMsg">
<item name="callsign" type="std::string"/>
<item name="message" type="std::string"/>
<item name="system" type="bool"/>
</message>
<message name="LoginMsg">
<include file="Ids.h"/>
<item name="login" type="std::string"/>
<item name="password" type="std::string"/>
<item name="avatarId" type="AvatarId"/>
</message>
</messages>


L'on remarquera le node include dans la définition du LoginMsg, qui permet d'ajouter des inclusions supplémentaires pour les types non standard, en l'occurence mon AvatarId.

L'on passe à la moulinette, et voilà le travail:


$ wc -l *Msg.{h,cpp}
26 LoginMsg.h
21 PingMsg.h
25 RadioMsg.h
47 LoginMsg.cpp
31 PingMsg.cpp
47 RadioMsg.cpp
197 total


La génération de code avec Ocaml est toujours un plaisir, la bibliothèque standard est bourrée de fonctions particulièrement utiles dans ce contexte: String.uppercase, String.capitalize, String.concat, et l'ensemble du programme tient pour l'instant en 117 lignes. Bien entendu, la génération du code est extrêmement rapide, je doute avoir un jour suffisamment de messages pour qu'elle ne paraisse plus instantanée.

Pour les curieux, le code est , mais attention, c'est très lourdement adapté à mes besoins, et donc probablement pas réutilisable en l'état.

mercredi 11 mars 2009

Qu'est ce qu'on fait, on s'risque sur le bizarre ?



Voilà ce qui s'est passé quand j'ai appliqué ma texture de bruit, non pas à ma texture dans le fragment shader, mais à mon vecteur dans le vertex shader! Ma sphère se met à effectuer une sorte de danse du ventre, en se déformant à l'horizontale (il me semble d'ailleurs que ce n'est que depuis une récente norme que l'on peut accéder aux données de textures dans le vertex shader).

Pas sûr que ça soit très utile, mais c'est rigolo.

mardi 10 mars 2009

Animation!

L'animation marche! L'impression générale en est un cube au fond de la piscine. Ça bouge comme je veux, et c'est plutôt efficace. L'idée est en fait totalement basée sur le shader précédent. Tout d'abord, j'ai rendu mon bruit 2D au lieu de 1D, en utilisant la composante rouge et la composante verte. Il est également important que l'image de bruit n'aie pas de bords, que l'on puisse paver la texture sans jointures apparentes.



Ensuite, je maintiens un flotant qui correspond à mon offset sur la texture de bruit, et je me contente de décaler ma texture de bruit avant d'appliquer le mouvement à ma texture principale.

Malheureusement, j'ai des difficultés à faire des captures de vidéo, mais promis, si j'ai, je poste!

dimanche 8 mars 2009

Enfin un shader utile

Ce n'est pas faute de m'être battu avec, mais pour l'instant, les seuls shaders que j'arrivais à faire fonctionner reproduisaient de fait le pipeline fixe de la carte vidéo, c'est à dire la même chose en moins bien et en moins efficace.

J'ai eu l'impression que le nombre de tutoriels sur les shaders était extrêmement réduit, surtout en ce qui concerne GLSL. Autant vous trouverez toujours des vidéos hallucinantes d'effets d'eau et autres, autant dès qu'il s'agit de montrer son code, tout le monde devient si pudique, les plus audacieux vous renvoyant vers l'épais bouquin "OpenGL shading language", lequel n'est quand même pas donné (mais si ça continue, je vais me laisser tenter!).

Basé sur ce tutoriel, la page de NeHe, cet autre là et les exemples d'OpenSceneGraph, voilà mon premier effet non trivial: la texture molle!

Partons d'un cube texturé tout bête:



L'idée est de déplacer les coordonnées de texture, en se servant d'une texture de "bruit". Cette texture va contenir de fait le décalage des coordonnées de l'image. Voilà la texture de bruit que j'ai utilisée.



Il ne reste plus qu'à charger les shaders.

Vertex:


varying vec3 N;
varying vec3 v;
varying vec2 texture_coordinate;
void main(void)
{
v = vec3(gl_ModelViewMatrix * gl_Vertex);
N = normalize(gl_NormalMatrix * gl_Normal);
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
texture_coordinate = vec2(gl_MultiTexCoord0);
}


Fragment:

varying vec3 N;
varying vec3 v;
varying vec2 texture_coordinate;
uniform sampler2D color_texture;
uniform sampler2D noise_texture;
void main(void)
{
vec3 L = normalize(gl_LightSource[0].position.xyz - v);
vec4 Idiff = gl_FrontLightProduct[0].diffuse * max(dot(N,L), 0.0);
Idiff = clamp(Idiff, 0.0, 1.0);

vec2 new_coord = texture_coordinate + vec2(texture2D(noise_texture, texture_coordinate)) / 255;

gl_FragColor = Idiff * texture2D(color_texture, new_coord);
}


Surtout, ne pas oublier de passer les deux textures en tant qu'uniform, pour color_texture et noise_texture.

Et voilà le résultat!



Les prochaines étapes seraient:
- Utiliser les canaux rouge et vert de la texture, afin d'avoir un déplacement qui n'aille pas dans la même direction en x et en y
- Envoyer également une texture unidimensionnelle, correspondant à la quantité d'effet que l'on veut mettre (par exemple, une sinusoïde), afin d'animer la texture

vendredi 6 mars 2009

Un binder en C++ pour libpqxx

Une fois de trop, je me suis retrouvé à écrire un horrible bout de code spécifique pour extraire mes données de ma belle base postgresql. Quand j'ai l'impression d'écrire plusieurs fois la même chose, j'essaie d'en faire un utilitaire. J'ai donc commencé à balancer quelques templates pour faire le gros du boulot pour moi, et voilà ce que ça donne, le tout tenant dans un petit en-tête (si vous voulez l'utiliser, n'oubliez pas d'ajouter les #include pour libpqxx et boost::optional, et probablement de virer mon support pour les StrongId).

L'utilisation du bazar est fort simple. Tout d'abord, définir une classe qui contiendra les données de la base, et qui possède une méthode statique qui passe les champs au binder, dans l'ordre de la requête:


struct PropertyListBinder
{
PropertyListId m_listId;
std::string m_key;
std::string m_valueType;
std::string m_accessType;
boost::optional m_intValue;
boost::optional m_doubleValue;
boost::optional m_stringValue;

static void bind(DbBinder & binder)
{
binder.bind(&PropertyListBinder::m_listId);
binder.bind(&PropertyListBinder::m_key);
binder.bind(&PropertyListBinder::m_valueType);
binder.bind(&PropertyListBinder::m_accessType);
binder.bind(&PropertyListBinder::m_intValue);
binder.bind(&PropertyListBinder::m_doubleValue);
binder.bind(&PropertyListBinder::m_stringValue);
}
};


L'on peut passer directement les boost::optional, qui seront interprétés comme des champs pouvant être nuls. Une valeur nulle sur un champ non optionnel lèvera une exception.

Pour l'appel, l'on créé un binder, un vecteur de notre structure qui contiendra les données, et l'on combine tout ça avec un résultat de libpqxx.


pqxx::result result = ...

DbBinder binder;

std::vector propertyListBinders;
binder.extract(propertyListBinders, result);


Le vecteur est rempli! C'est quand même plus facile que de se trimballer avec les appels à as et les is_null, non?

mardi 3 mars 2009

Les Property Lists

Avez-vous lu le post de Steve Yegge appelé "The Universal Design Pattern"? Allez-y. C'est du touffu, mais comme d'habitude, c'est très intéressant. Steve y présente une structure de données qui correspond à une liste de propriétés, et un système d'héritage permettant à une liste de dériver d'une autre.

Cette structure est probablement réinventée par tout programmeur un jour ou l'autre, bien que Steve l'étende bien plus loin.

C'est donc sur ce type de structures qu'une partie importante de la persistence et de la communication client-serveur se base. L'idée étant que l'on ne connaît pas par avance l'ensemble des paramètres qui affectent un objet du jeu, mais également que certains paramètres n'ont de sens que pour le client (par exemple, les informations relatives au modèle 3D à charger). Dans ce cas là, pourquoi est-ce que le serveur devrait comprendre ces données? Non, il les passe directement au client.

Voilà ma structure de données en SQL:


create table PropertyListDescriptions(
listId int primary key,
parentId int null references PropertyListDescriptions(listId));

create table PropertyListTypes(
valueType varchar(255) primary key);
insert into PropertyListTypes values('int'), ('double'), ('string');

create table PropertyListAccess(
accessType varchar(255) primary key);
insert into PropertyListAccess values('restricted'), ('private'), ('public');

create table PropertyListItems(
listId int not null references PropertyListDescriptions(listId),
key varchar(255) not null,
valueType varchar(255) not null references PropertyListTypes(valueType),
accessType varchar(255) not null references PropertyListAccess(accessType),
intValue int null,
doubleValue double precision null,
stringValue text null);


Une liste est simplement définie par un numéro, et peut avoir une liste parente.
Un élément de liste est associé au numéro de la liste, et possède une clé (le nom de la propriété), et une valeur typée (pour l'instant, une chaîne, un entier ou un double). L'accès me sert à déterminer qui a le droit de voir la propriété:
- une propriété publique (par exemple, la couleur de mes cheveux) est envoyée à tous les clients
- une propriété privée (par exemple, le contenu de mon sac) n'est envoyée qu'au client qui possède la liste
- une propriété restreinte (par exemple, le nombre de charges magiques dans mon poignard) ne sort jamais du serveur, car même son possesseur n'est pas sensé connaître l'information.

C'est simple et efficace, la seule chose manquante étant pour une liste de contenir une autre liste (par exemple, la liste associée à un personnage contient une sous-liste correspondant à son inventaire). Autant descendre à ce niveau rend la structure beaucoup plus dynamique, autant je suis un peu inquiet de ne pas m'y retrouver! Pour l'instant, j'ai donc l'intention d'avoir mes objets principaux (un personnage, par exemple) codés en dur, et composé de plusieurs listes, par exemple la liste d'inventaire, la liste de caractéristiques, etc. Et si j'ai du courage, je passerai aux listes dans les listes!

dimanche 1 mars 2009

Kaboom!

Dans la série "Qu'est-ce qu'on s'amuse avec Blender", voilà une bouche à feu, qui complétera le phare. L'objet est totalement phallique, mais allez modeler un canon qui aie de la gueule sans être obscène!



La modélisation est toute bête: je pars d'une UV sphère 8 par 8, j'étends l'anneau juste après l'équateur pour faire le corps de la bête, puis je sélectionne l'anneau d'après, le recule un poil et le resserre pour faire la bouche, puis sélectionne les pôles pour les renvoyer au fond du canon.



Le soutien est des plus simples, un bête cylindre que j'extrude pour le faire rentrer en lui-même.



La bête gagnerait à avoir plus de polygones, et une bonne renormalisation pour avoir à la fois un corps qui semble circulaire et une bouche bien droite. Ou alors, lancer des boulets au profil octogonal?

Un phare

Dans la continuité de mes expérimentations pour avoir un décor qui tente de donner envie à rester dans le jeu, voilà un phare! Comme d'habitude, je laisse les textures pour l'instant, et je me concentre sur le modelage pour créer un objet qui soit à la fois reconnaissable et avec un nombre restreint de polygones.



Blender rend la chose plutôt mieux que OSG, mais pouvoir mettre une lumière à l'intérieur aide beaucoup. Si je devais améliorer la chose, je mettrais un petit balcon tout autour de la lanterne, une porte d'entrée.