L'objectif du projet en termes d'implémentation était de réaliser un jeu en 3D. Comme il n'y avait pas de bibliothèque permettant de développer de la 3D dans l'environnement de programmation Bigloo, le port d'APIs multimédia déjà existantes en C a dû être effectué. Il a fallu effectuer le binding de deux bibliothèques:
Pour la gestion de la 3D, le choix s'est porté sur la bibliothèque OpenGL. C'est cette bibliothèque qui s'occupe de toutes les opérations mathématiques inhérentes à la programmation 3D en général.
Pour pouvoir s'interfacer avec l'utilisateur, la bibliothèque C SDL a été retenue. C'est elle qui permet d'ouvrir une fenêtre, de récupérer les événements clavier, souris, etc... Pour celle-ci, contrairement à OpenGL, un binding minimal a été effectué.
Au dessus de ces deux interfaçages "bruts", un moteur 3D a été réalisé, dont le but est de permettre une utilisation plus aisée d'OpenGL en offrant des fonctionnalités et des notions d'assez haut niveau, non présentes en standard dans cette librairie.
Le moteur 3D constitue un sur-ensemble de classes Scheme utilisant les deux bindings décris précédemment. Il n'est ainsi pour une grosse partie qu'un "emballage" utilitaire, réutilisant principalement les sous fonctions C présentes dans les bibliothèques d'origine.
Ci-dessous, un petit descriptif de l'architecture et des fonctionnalités offertes par le moteur:
Il fournit un ensemble de classes EVector, représentant l'élément incontournable dans la programmation 3D. Plusieurs déclinaisons sont mises à disposition:
EVector2d : pour représenter un vecteur de deux doubles,
EVector2f : pour un vecteur de deux floats,
EVector3f : pour un vecteur de trois floats,
etc ...
Chacune de ces classes dérive de la classe générique EVector, et fournit les fonctions primitives de manipulation des vecteurs, comme:
eq-vector? : prédicat qui rend #t si deux vecteurs sont égaux,
+evector : addition vectorielle (de même pour -evector),
mult-scalar-vector : produit d'un vecteur par un scalaire,
cross-prod! : produit vectoriel de deux vecteurs,
dot-prod : produit scalaire,
et encore bien d'autres fonctions...
Un module EUtils fournit quelques fonctions utilitaires comme
conversion basiques: deg->rad, rad->deg,
changements de repère: spherique->cartesian, cartesian->spherique.
Pour cette dernière fonctionnalité, la forme multiple-value-bind de Bigloo qui permet le retour de plusieurs valeurs a été fortement appréciée.
Il fournit également un ensemble de classes dérivant de ERenderable, classe qui représente une primitive graphique et qui met à disposition des méthodes génériques servant au rendu de primitives graphiques:
render et inner-render : servent a faire le rendu OpenGL d'un objet,
add-attribs : permet de lui ajouter des attributs (couleur,texture).
Les principales primitives 3D dérivent de ERenderable, à savoir:
ELine
ETriangle,
EQuad,
EPolygon ..
De plus, le moteur fournit la classe Egroup qui permet de créer un groupe logique d'objets de même type (par exemple ETriangle). Cela a pour effet d'optimiser le rendu OpenGL, car ainsi regroupés en entités identiques, les appels à OpenGL à effectuer se retrouvent minimisée.
Dans l'optique de regrouper des primitives en entités logiques, la classe ECollection permet de rassembler des primitives ERenderable, mais cette fois quels que soient leur type. C'est une classe utilitaire permettant uniquement de simplifier l'écriture de programmes Scheme avec le moteur.
Les attributs sont implémentés dans le module EAttribut par les classes EAttrVN, EAttrColor et EAttrTexture, qui servent respectivement à représenter un vecteur normal, une couleur RGBA et des coordonnées de texture.
Chacune de ces classes contient la méthode générique apply-attribut, permettant de notifier à OpenGL les attributs à prendre en compte durant le rendu d'un ERenderable.
La classe ECamera, est une sofistication non présente dans OpenGL, qui sert à simuler l'existence d'une vrai caméra. Elle permet de spécifier toute sorte d'action réalisable par une caméra, et de modifier :
sa position dans le monde,
sa direction,
la cible qu'elle regarde,
sa focale (effet grand angle, ou plan serré)...
De plus, il existe des méthodes permettant de faire effectuer à la caméra des mouvements plus complexes:
translate-camera: translation,
translate-camera-by-fix-target : translation en regardant la cible restée immobile
rotate-camera : rotation sur elle même,
rotate-camera-by-fix-target : rotation autour de la cible
translate-target-camera: translation de la cible, sans déplacer la caméra
Cette classe contient aussi la méthode apply-camera-transformation qui effectue le "rendu" de la caméra; il n'y a ainsi plus aucune opération OpenGL à exécuter.
Une classe ELight également fournie sert à représenter une lumière. Il est possible de préciser pour une lumière:
sa position,
tous les attributs d'OpenGL : diffusion, spéculaire, directivité
Mais on peut également lui affecter une cible et ainsi lui faire effectuer un mouvement relatif à celle-ci, de manière semblable à la caméra.
Enfin, la dernière classe du moteur - mais pas la moins importante - est la classe EEngine.
Elle simplifie encore l'usage des lumières et des caméras en offrant les fonctionnalités suivantes à l'utilisateur:
ajout d'une caméra,
activation d'une caméra,
ajout d'une lumière...
Mais surtout grâce à la méthode engine-flush-render, qui assure le rendu de toutes les lumières présentes dans la scène et de la camera active, ce qui permet de ne pas se préoccuper de ces opérations OpenGL fastidieuses.