void Application::drawRWB() { glPushAttrib(GL_ALL_ATTRIB_BITS); glPushMatrix(); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glBegin(GL_QUADS); glColor3f(0.5, 1.0, 0.5); // Horizontal Screen glVertex3f(-0.9, -0.91, 1.23); glVertex3f(0.9,-0.91,1.23); glVertex3f(0.9,-0.63,-0.075); glVertex3f(-0.9,-0.63,-0.075); // Vertical Screen glVertex3f(-0.9,-0.63,-0.075); glVertex3f(0.9,-0.63,-0.075); glVertex3f(0.9,0.72,-0.075); glVertex3f(-0.9,0.72,-0.075); glEnd(); glPopMatrix(); glPopAttrib(); }
Cette méthode sera appelée directement dans la méthode draw de votre programme lorsque vous le testerez sur station, pour représenter dans l'espace virtuel le cadre du workbench et faciliter le placement des éléments graphiques. Elle ne sera utile que dans la simulation sur station de travail et vous pourrez retirer (ou mettre en commentaire) son appel lorsque vous passerez sur le workbench pour tester votre application.
Etudiez et commentez la signification des valeurs numériques utilisées dans les glVertex.
La technique du rayon virtuel, très utilisée en RV, consiste à visualiser la direction pointée par le périphérique d'interaction (le flystick) par un rayon virtuel. Il est représenté graphiquement comme une simple ligne, de très grande longueur. Son placement est relatif au flystick et sa position doit être mise a jour régulièrement à chaque frame (dans la méthode preFrame).
Une fois que le rayon virtuel suit correctement les mouvements et les orientations du flystick, il faut ensuite être capable de détecter l'intersection du rayon virtuel avec les objets de la scène. Créez dans votre scène deux boites cubiques (chacune possédant sa matrice de positionnement dans le repère du monde) et ajoutez le code nécessaire pour détecter l'intersection du rayon avec les éléments de la scène :
bool Application::intersectCube(gmtl::Vec3f origin, gmtl::Vec3f dir, gmtl::Matrix44f cubeMat) { gmtl::Rayray(origin, dir); gmtl::Tri tri[12]; float u,v,t; bool res = false; // 8 sommets gmtl::Vec4f A, B, C, D, E, F, G, H; gmtl::Vec3f a, b, c, d, e, f, g, h; A.set(0.0f, 0.0f, 0.0f, 1.0f); B.set(1.0f, 0.0f, 0.0f, 1.0f); C.set(1.0f, 1.0f, 0.0f, 1.0f); D.set(0.0f, 1.0f, 0.0f, 1.0f); E.set(0.0f, 0.0f, 1.0f, 1.0f); F.set(1.0f, 0.0f, 1.0f, 1.0f); G.set(1.0f, 1.0f, 1.0f, 1.0f); H.set(0.0f, 1.0f, 1.0f, 1.0f); A = cubeMat * A; B = cubeMat * B; C = cubeMat * C; D = cubeMat * D; E = cubeMat * E; F = cubeMat * F; G = cubeMat * G; H = cubeMat * H; a.set(A[0],A[1],A[2]); b.set(B[0],B[1],B[2]); c.set(C[0],C[1],C[2]); d.set(D[0],D[1],D[2]); e.set(E[0],E[1],E[2]); f.set(F[0],F[1],F[2]); g.set(G[0],G[1],G[2]); h.set(H[0],H[1],H[2]); // 2 triangles par face tri[0].set(a,b,c); tri[1].set(c,d,a); tri[2].set(e,f,g); tri[3].set(g,h,e); tri[4].set(f,b,c); tri[5].set(c,g,f); tri[6].set(a,e,h); tri[7].set(h,d,a); tri[8].set(h,g,c); tri[9].set(c,d,h); tri[10].set(a,b,f); tri[11].set(f,e,a); res = intersect(tri[0],ray,u,v,t) || intersect(tri[1],ray,u,v,t) || intersect(tri[2],ray,u,v,t) || intersect(tri[3],ray,u,v,t) || intersect(tri[4],ray,u,v,t) || intersect(tri[5],ray,u,v,t) || intersect(tri[6],ray,u,v,t) || intersect(tri[7],ray,u,v,t) || intersect(tri[8],ray,u,v,t) || intersect(tri[9],ray,u,v,t) || intersect(tri[10],ray,u,v,t) || intersect(tri[11],ray,u,v,t); return res; }
Ce code s'appuie sur la fonction gmtl suivante : intersect qui détecte l'intersection d'un triangle et d'un rayon.
Une fois que le rayon détecte correctement les intersections avec les objets de la scène, ajoutez les instructions nécessaires pour permettre de saisir et déplacer un objet sélectionné lorsque l'utilisateur clique sur un bouton du flystick et tant qu'il le maintient.
Pour cela vous devez sauvegarder la matrice de positionnement et d'orientation du rayon virtuel au moment du clic initial dans le repère du monde (le repère des trackers) : M(w<-f). W pour world (repere du monde) et f pour Flystick (le joystick tenu en main d'ou part le rayon virtuel). Appelons d'autre part M(w<-o) la matrice de positionnement/orientation de l'objet dans le repère du monde.
L'objet saisi par le rayon est positionné/orienté dans le repère local du rayon/flystick par la matrice : M(f<-o) = M(f<-w) * M(w<-o) = M(w<-f)^(-1) * M(w<-o). L'inversion est réalisée par la fonction gmtl::makeInvert. Cette matrice M(f<-o) est invariante tant que l'on aggripe l'objet ! L'objet est solidaire du rayon pendant la manipulation.
A chacune des frames suivantes (tant que le clic est maintenu),
Le menu restera fixe dans cette première version. Ajoutez les instructions nécessaires à votre programme pour que l'intersection du rayon virtuel manipulé par l'utilisateur avec les boutons du menu déclenchent un changement de couleur des boutons (pour les "illuminer" quand on passe dessus).
De plus, lorsque l'utilisateur "clique" (ie appuie sur un des boutons du flystick), si le rayon intersecte un bouton, l'action correspondante doit etre déclenchée. Testez en changeant par exemple la couleur d'un cube dans la scène en fonction du bouton choisi (bleu ou rouge).
Faites en sorte à présent que le socle du menu puisse etre déplacé lorsqu'il est sélectionné par le rayon (en drag and drop comme précédemment. N'oubliez pas que lorsque le socle bouge... les boutons aussi).
Nous allons à présent modifier votre programme pour que la position du menu suive la position de la main non dominante de l'utilisateur. Notez que la sélection des boutons se fera toujours avec la main dominante qui controle le rayon et que l'interaction devient bi-manuelle. Nous allons donc utiliser les gants de données, qui vont remplacer le flystick.
Pour utiliser les gants vous devez définir
dans la classe simpleApp :
gadget::PositionInterface mRightHand, mLeftHand; gadget::AnalogInterface mRightThumb, mRightIndex, mRightMiddle; gadget::AnalogInterface mLeftThumb, mLeftIndex, mLeftMiddle;dans la méthode init :
mRightHand.init("VJRightHand"); mRightIndex.init("RightIndexFlex"); //etc...dans la méthode preFrame
gmtl::Vec3f rightGlovePos(mRightHand->getData(1.0)[0][3], mRightHand->getData(1.0)[1][3], mRightHand->getData(1.0)[2][3]); // pour recupérer les valeurs de tracker des gants mLeftIndex.getProxy()->getData() renvoie une valeur entre 0 et 1 de flexion du doigt qui peut être testé pour "cliquer" en fermant la main
En repartant de votre programme actuel, changer l'interaction pour que l'utilisateur sélectionne les objets/boutons/menus en plongeant la main à l'intérieur (il faut écrire une fonction pour détecter si un pointeur se situe "dans" un volume cubique). "Cliquer" équivaudra à refermer le poing.
Comparer vos impressions avec les deux méthodes.