Réalisation d'un hologramme 3d basé sur le principe de réflexion d'image sur une pyramide transparente.
Projet réalisé avec Cédric Bitsch.
Rendu 3D en OpenGL.
Guêpe modélisée dans Cinema 4D.
Pour exporter notre guêpe nous avons utilisé un script Python dans Cinema 4D afin d’obtenir les coordonnées des points
et des polygones sous forme de tableaux en langage C.
Chaque partie du corps de la guêpe est indépendante des autres, ce qui permet d’animer l’insecte.
Nous avons utilisé une méthode hiérarchique pour lier les éléments entre eux (méthode push / pop).
glPushMatrix();
dessineObjet(&obj_Guepe);
dessineObjet(&obj_Corps); // A un enfant
glPushMatrix();
dessineObjet(&obj_Tete); // A un enfant
glPushMatrix();
dessineObjet(&obj_AntenneG); // A un suivant
glPopMatrix();
glPushMatrix();
dessineObjet(&obj_AntenneD); // Suivant
glPopMatrix();
glPopMatrix();
glPushMatrix();
dessineObjet(&obj_Cul); // Parent, Suivant
glPopMatrix();
glPushMatrix();
dessineObjet(&obj_PatteD1); // Suivant
glPopMatrix();
glPushMatrix();
dessineObjet(&obj_PatteD2); // Suivant
glPopMatrix();
glPushMatrix();
dessineObjet(&obj_PatteD3); // Suivant
glPopMatrix();
glPushMatrix();
dessineObjet(&obj_PatteG1); // Suivant
glPopMatrix();
glPushMatrix();
dessineObjet(&obj_PatteG2); // Suivant
glPopMatrix();
glPushMatrix();
dessineObjet(&obj_PatteG3); // Suivant
glPopMatrix();
glPushMatrix();
dessineObjet(&obj_AileD1); // Suivant
glPopMatrix();
glPushMatrix();
dessineObjet(&obj_AileD2); // Suivant
glPopMatrix();
glPushMatrix();
dessineObjet(&obj_AileG1); // Suivant
glPopMatrix();
glPushMatrix();
dessineObjet(&obj_AileG2); // Suivant
glPopMatrix();
glPopMatrix();
Nous avons créé une rotation automatique des antennes et des pattes afin de créer un effet de réalisme.
Nous avons utilisé 2 rotations dans 2 axes différents, chacune animée d’un mouvement sinusoïdal décalé afin d’obtenir un mouvement circulaire.
Au sol notre guêpe adapte l’allure du mouvement de ses pattes suivant la vitesse de déplacement.
Nous avons un système de rotation identique à celui des antennes.
// Animation pattes
float vitesse = sqrt(_obj->vit.x * _obj->vit.x + _obj->vit.z * _obj->vit.z);
vitesse *= 50;
vitesse *= 1. - limite(_obj->posAbs.y, 0.0, 1.0);;
if (_obj->posAbs.y < 0.1) {
// Patte avant gauche
obj_PatteD1.rotcos += 0.1 * vitesse;
limCos(&obj_PatteD1.rotcos);
obj_PatteD1.angle = cos(obj_PatteD1.rotcos) * 20;
obj_PatteD1.rotation = {0., 1., 0.};
obj_PatteD1.rotcos2 += 0.1 * vitesse;
limCos(&obj_PatteD1.rotcos2);
obj_PatteD1.angle2 = cos(obj_PatteD1.rotcos2 + PIs2) * 20;
obj_PatteD1.rotation2 = {0., 0., 1.};
// Patte avant droite
obj_PatteG1.rotcos += -0.1 * vitesse;
limCos(&obj_PatteG1.rotcos);
obj_PatteG1.angle = cos(obj_PatteG1.rotcos) * 20;
obj_PatteG1.rotation = {0., 1., 0.};
obj_PatteG1.rotcos2 += -0.1 * vitesse;
limCos(&obj_PatteG1.rotcos2);
obj_PatteG1.angle2 = cos(obj_PatteG1.rotcos2 + PIs2) * 20;
obj_PatteG1.rotation2 = {0., 0., 1.};
// Patte milieu gauche
obj_PatteD2.rotcos += 0.1 * vitesse;
limCos(&obj_PatteD2.rotcos);
obj_PatteD2.angle = cos(obj_PatteD2.rotcos + PIs2) * 20;
obj_PatteD2.rotation = {0., 1., 0.};
obj_PatteD2.rotcos2 += 0.1 * vitesse;
limCos(&obj_PatteD2.rotcos2);
obj_PatteD2.angle2 = cos(obj_PatteD2.rotcos2) * 20;
obj_PatteD2.rotation2 = {0., 0.5, 1.};
// Patte milieu droite
obj_PatteG2.rotcos += -0.1 * vitesse;
limCos(&obj_PatteG2.rotcos);
obj_PatteG2.angle = cos(obj_PatteG2.rotcos + 1.7) * 20;
obj_PatteG2.rotation = {0., 1., 0.};
obj_PatteG2.rotcos2 += 0.1 * vitesse;
limCos(&obj_PatteG2.rotcos2);
obj_PatteG2.angle2 = cos(obj_PatteG2.rotcos2 + PIs2) * 20;
obj_PatteG2.rotation2 = {0., 0., 1.};
// Patte arrère gauche
obj_PatteD3.rotcos += 0.1 * vitesse;
limCos(&obj_PatteD3.rotcos);
obj_PatteD3.angle = cos(obj_PatteD3.rotcos) * 20;
obj_PatteD3.rotation = {0., 1., 0.};
obj_PatteD3.rotcos2 += 0.1 * vitesse;
limCos(&obj_PatteD3.rotcos2);
obj_PatteD3.angle2 = cos(obj_PatteD3.rotcos2 + PIs2) * 20;
obj_PatteD3.rotation2 = {1., 0., 0.};
// Patte arrère droite
obj_PatteG3.rotcos += -0.1 * vitesse;
limCos(&obj_PatteG3.rotcos);
obj_PatteG3.angle = cos(obj_PatteG3.rotcos) * 20;
obj_PatteG3.rotation = {0., 1., 0.};
obj_PatteG3.rotcos2 += 0.1 * vitesse;
limCos(&obj_PatteG3.rotcos2);
obj_PatteG3.angle2 = cos(obj_PatteG3.rotcos2 + PIs2) * 20;
obj_PatteG3.rotation2 = {1., 0., 0.};
}
}Lors de son envol nous avons un mouvement de repli des pattes.
Nous avons également un mouvement des ailes basé sur le même principe d’accélération que pour le mouvement au sol.
Nous avons créé 2 lumières :
- La première est une lumière ambiante (couleur froide).
- La seconde est un projecteur (couleur chaude) qui met en relief la guêpe.
Nous avons 4 caméras :
- La première pour la vue pleine écran qui tourne autour de la guêpe.
- Les 3 autres sont réservé pour afficher l’hologramme.
Vecteur calculeCam(int ax, int ay) {
Vecteur _c = {1,1,1};
_c.x = 2 * sin(ax * 3.14 / 180.) * cos(ay * 3.14 / 180.) + cameraPos.x;
_c.y = 2 * sin(ay * 3.14 / 180.) + cameraPos.y;
_c.z = 2 * cos(ax * 3.14 / 180.) * cos(ay * 3.14 / 180.) + cameraPos.z;
return _c;
}
Vecteur calculeU(Vecteur camPos, Vecteur ciblePos, int idCam) {
Vecteur _v1 = vecteurMoins(camPos, ciblePos);
Vecteur _v2 = vecteurMoins(camPos, ciblePos); _v2.y = 0;
Vecteur _u;
if (idCam == 2) { // Milieu
Vecteur _w = produitVectoriel(_v1, _v2);
_u = produitVectoriel(_v1, _w);
_u = vecteurProduit(_u, -1);
if (camPos.y - ciblePos.y < 0) _u = vecteurProduit(_u, -1);
}
else if (idCam == 1) { // Gauche
_u = produitVectoriel(_v1, _v2);
_u = vecteurProduit(_u, -1);
if (camPos.y - ciblePos.y < 0) _u = vecteurProduit(_u, -1);
}
else if (idCam == 3) { // Droite
_u = produitVectoriel(_v1, _v2);
if (camPos.y - ciblePos.y < 0) _u = vecteurProduit(_u, -1);
}
else if (idCam == 4) { // Gauche
Vecteur _w = produitVectoriel(_v1, _v2);
_u = produitVectoriel(_v1, _w);
if (camPos.y - ciblePos.y < 0) _u = vecteurProduit(_u, -1);
}
return _u;
}
// [...]
gluPerspective(60., 1920. / 1080. , 0.01, 40.);
Vecteur camPos = calculeCam(anglex, angley);
Vecteur _u = calculeU(camPos, obj_Guepe.posAbs, 4);
gluLookAt(camPos.x, camPos.y, camPos.z, // Position de la caméra
obj_Guepe.posAbs.x, obj_Guepe.posAbs.y, obj_Guepe.posAbs.z, // Position où la cam regarde
_u.x, _u.y, _u.z); // Vecteur haut
Nous avons trois textures :
- Une pour la guêpe.
- Une pour le sol.
- Une pour les arbres.
Nous avons utilisé une fonction qui lit les fichiers bmp pour importer les textures dans GLUT.
GLuint LoadTexture(const char * filename, int width, int height) {
GLuint texture;
unsigned char * data;
FILE* file;
file = fopen(filename, "rb");
if (file == NULL) return 0;
data = (unsigned char *)malloc(width * height * 3);
//int size = fseek(file,);
fread(data, width * height * 3, 1, file);
fclose(file);
for (int i = 0; i < width * height ; ++i) {
int index = i*3;
unsigned char B,R;
B = data[index];
R = data[index+2];
data[index] = R;
data[index+2] = B;
}
glGenTextures( 1, &texture );
glBindTexture( GL_TEXTURE_2D, texture );
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_REPEAT );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_REPEAT );
gluBuild2DMipmaps( GL_TEXTURE_2D, 3, width, height,GL_RGB, GL_UNSIGNED_BYTE, data );
free( data );
return texture;
}
Nous avons calculé les normales des sommets manuellement.
- La normale d’un sommet = moyenne des normales des polygones autours du sommet.
- La normale d’un polygone = produit vectoriel de 2 côtés adjacents.
Les normales permettent d’avoir un rendu plus lisse de l’objet face à la lumière.
void calculeNormaleObjet(Objet* _obj) {
Vecteur (*_normales)[4] = _obj->normales;
Vecteur *_p = _obj->points;
int (*_f)[4] = _obj->polys;
int nbPolys = _obj->nbpolys;
int nbPts = _obj->nbpts;
int i;
Vecteur tabNorPts[nbPts];
for (i = 0 ; i < nbPts ; i++) {
tabNorPts[i] = {0.0, 0.0, 0.0};
}
for (i = 0 ; i < nbPolys ; i++) {
Vecteur normalePoly = calculeNormalePol(_p[_f[i][0]], _p[_f[i][1]], _p[_f[i][2]], _p[_f[i][3]]);
tabNorPts[_f[i][0]] = vecteurPlus(tabNorPts[_f[i][0]], normalePoly);
tabNorPts[_f[i][1]] = vecteurPlus(tabNorPts[_f[i][1]], normalePoly);
tabNorPts[_f[i][2]] = vecteurPlus(tabNorPts[_f[i][2]], normalePoly);
tabNorPts[_f[i][3]] = vecteurPlus(tabNorPts[_f[i][3]], normalePoly);
}
for (i = 0 ; i < nbPts ; i++) {
tabNorPts[i] = vecteurNormaliser(tabNorPts[i]);
}
for (i = 0 ; i < nbPolys ; i++) {
_normales[i][0] = tabNorPts[_f[i][0]];
_normales[i][1] = tabNorPts[_f[i][1]];
_normales[i][2] = tabNorPts[_f[i][2]];
_normales[i][3] = tabNorPts[_f[i][3]];
}
free(tabNorPts);
}
Flèches directionnelles : Avancer, reculer, pivoter à droite, pivoter à gauche.
Espace ou + : Grand saut
C : Petit saut
H ou * : Vue hologramme / vue 3d classique
/ : Attaque dard
Q : Quitter
Nous avons rencontré une difficulté en voulant créer automatiquement la génération des push et des pop.
Nous voulions gérer la mise en place des différentes parties de notre guêpe dans une fonction.
Cette fonction aurait été très bénéfique car nous aurions pu intégrer dans notre projet n’importe
quel « robot » sans avoir à gérer manuellement les push et les pop.
Nous avions commencé a créer un système de hiérarchie entre les différents éléments de la guêpe grâce à un système de liste chainé.
obj_Guepe.enfant = &obj_Corps;
obj_Corps.parent = &obj_Guepe;
obj_Corps.enfant = &obj_Tete;
obj_Tete.parent = &obj_Corps;
obj_Tete.enfant = &obj_AntenneG;
obj_Tete.suivant = &obj_PatteD1;
obj_AntenneG.parent = &obj_Tete;
obj_AntenneG.suivant = &obj_AntenneD;
obj_AntenneD.parent = &obj_Tete;
obj_AntenneD.precedent = &obj_AntenneG;
obj_PatteD1.parent = &obj_Corps;
obj_PatteD1.precedent = &obj_Tete;
obj_PatteD1.suivant = &obj_PatteD2;
obj_PatteD2.parent = &obj_Corps;
obj_PatteD2.precedent = &obj_PatteD1;
obj_PatteD2.suivant = &obj_PatteD3;
obj_PatteD3.parent = &obj_Corps;
obj_PatteD3.precedent = &obj_PatteD2;
obj_PatteD3.suivant = &obj_Cul;
Une des parties les plus délicates du projet a été la mise en place des 3 vues pour générer l’hologramme.
Nous avons rencontré des difficultés sur l’aménagement et l'angle des vues. Au final nous avons réussi à figer les vues et à obtenir un très bel hologramme bien que les 3 écrans se superposent légèrement. Nous nous sommes aidé d'un logiciel 3D pour simuler les triangles et les réflexions sur les faces du plexiglas.
Nous avons réalisé un socle en bois avec une ouverture à l’arrière afin d’insérer l’ordinateur portable.
Nous avons créé un logo représentant notre guêpe.
Le support est totalement rétractable pour le rangement et le déplacement.
Nous avons opté pour un hologramme avec 3 plans de plexiglas.
Nous avons 1 pièce centrale et 2 pièces pour les cotés. Nous avons remarqué que la réflexion de l'écran se faisait des deux côtés du plexiglas, ce qui dédoublait l'image. Une feuille de plexiglas très fine permet de réduire ce dédoublement mais la rend parfois trop souple.
keyboard_arrow_down