IMA5 2019/2020 P18
Sommaire
Présentation générale
Sujet : Virtual Reality Old Gaming
Etudiant : Ibrahim Ben Dhiab et Fabien Di Natale
Encadrant : Laurent Grisoni & Valentin Beauchamp
Description
L'équipe de recherche MINT, dans le cadre d'un projet européen, travaille sur un système permettant de jouer via un système de réalité virtuelle, à de vieux jeux tel que tetris et bomberman. Pour cela ils utilise un émulateurs logiciels dans une raspberry qui envoie le flux vidéo du jeu par wifi au casque. Ce dispositif est développé par l'équipe dans le cadre d'un projet européen portant sur l'utilisation de la RV (réalité virtuel) pour la rééducation. Le dispositif existant utilise un PC portable combiné à un casque de RV (Oculus Quest).
Nous souhaitons:
- faire l'adaptation nécessaire à l'allègement de la forme physique du dispositif, ou nous souhaitons passer à une version n'utilisant plus le portable, mais une émulation sur carte Rapsberry. Le travail de proposition d'architecture a déjà été fait pour ce point.
- faire en sorte d'étendre le système pour avoir une appli de configuration permettant de choisir la console et le jeu. Une partie de ces éléments de configuration pourront être utilisables dans le casque de RV par le malade, l'autre partie par le médecin, via une application tierce.
- éventuellement et en fonction de l'avancement du projet, mettre en place quelques scénarios d'interaction spécifiques, déjà déterminés, dans le dispositif interactif utile à l'avancée du projet européen.
Objectifs
Mettre en place quelques fonctionnalités dans un émulateur RV d'ancienne console
Préparation du projet
Cahier des charges
Le projet est fait suite à un besoin des hôpitaux et cliniques de rendre la rééducation moins compliquée et plus attrayante. Nous utilisons donc de vieux jeux vidéo en réalité virtuel afin d'amener les personnes à bouger comme si elle faisait des exercices tout en s'amusant rendant ainsi le processus de rééducation moins pénible et plus facile à suivre. De plus, toutes les personnes ayant besoin de rééducation ou ayant besoin de faire de l'exercice dans les hôpitaux ont des maladies ou ont eu des accidents très variés. Notre dispositif doit alors être polyvalent et facilement configurable par les médecins ou les infirmières qui serait amené à faire les rééducations.
Ainsi, nous allons nous concentrer sur la partie polyvalence et configuration du système. Nous avons pour objectif de rendre le système le plus polyvalent afin qu'il puisse convenir au plus de personnes possible. Le plus facilement et le plus rapidement utilisable par l'équipe médical.
Choix techniques : matériel et logiciel
Ce projet est fait à l'aide de unity avec les scripts en C# couplé à un serveur node.js sur une Raspberry pi donnant l'accès à une page web pour le personnel médical permettant la configuration et renvoyant la configuration à l'oculus. Nous utilisons un Oculus car c'est un casque de réalité virtuel de bonne qualité, dans des prix raisonnables mais surtout ne nécessitant pas d'ordinateur derrière pour faire tourner les jeux.
Liste des tâches à effectuer
Nous devons :
- Améliorer la page web afin de proposer les personnalisations de paramètre suivant:
- Choisir le nombre de manettes (type NES) utilisables par le patient
- Possibilité d'avoir juste les boutons que le personnel soignant pourra déplacer
- Ajout de script C# en concordance avec le serveur web
- Décoder correctement les configurations envoyant par la page web (la raspberry pi)
- Créer un groupe d'objet que de boutons sans la manette
- Créer une fonction pour déplacer tous les boutons séparément de la manette de base
- Vérifier si un scénario ayant pour objectif faire marcher le patient en déplaçant l'affichage du jeu et en fixant la manette NES sur les déplacements du casque est possible
Calendrier prévisionnel
Réalisation du Projet
Semaine 1
Pour ce projet, nous sommes intégrés à l'équipe mint s'occupant du contrat complet. Dans ce cadre nous pouvons alors découvrir complètement le sujet et voir les différentes personnes qui sont affectés à ce contrat.
Nous avons ensuite installé tous les logiciels nécessaires au lancement du projet sur l'Oculus:
- Télécharger le projet à partir du git
- Suivre les instructions des read.me (téléchargement de Unity, de certain assets et paramétrage d'Unity; téléchargement de node.js et paramétrage de la raspberry pi)
Ce sujet et divisé en deux parties:
- Une première partie web
- Une seconde partie Unity
Pour cette fin de première semaine nous avons pu découvrir la partie web qui est un serveur web écrit en node js. Le serveur web tourne sur la raspberry et a pour but de faire la liaison entre le praticien et le patient. Pour cela la raspberry héberge un serveur web sur lequel nous pouvons choisir la configuration du casque. Pour l'instant nous pouvons choisir entre:
- Contrôler le jeu à l'aide de la manette directement sur le serveur web
- Contrôler le jeu avec les pads matérialisé devant le joueur. Le nombre de pad peut varier de 1 à 3.
Semaine 2
Prise en main du projet avec un apprentissage des langages javascript, node.js et C# que nous avons peu utiliser pour le javascript et jamais utiliser pour les deux autres. Pour cette deuxième semaine dans l'équipe mint nous avons pu découvrir la seconde partie du projet sous unity. Celle-ci se compose principalement de deux chose, les scènes et les scripts permettant aux scènes d'évoluer.
La scène Unity
Pour les scènes nous en avons une seule composée des objets suivants :
- L'objet statique créant la pièce de jeu, c'est l'environnement que nous pouvons retrouver autour de nous lorsque nous avons le casque de réalité virtuelle devant les yeux. Cet objet est statique et n'est pas relié à des scripts ou autre d'où son nom.
- L'objet quad qui est un affichage reliè à 4 scripts, cet objet est l'endroit où l'image que la raspberry produit va apparaître, en d'autre mot, ça sera là où l'on peut voir le jeu comme une télévision. Les 4 scripts permettent la connexion et les échanges avec la raspberry, nous avons les scripts TCP config, TCP Commande, TCP screen et Vibration manager. TCP config permet de récupérer la configuration envoyée par la raspberry et lancer toutes les fonctions nécessaires afin de reconfigurer l'oculus selon la configuration reçue.
- L'objet Event System permet la liaison entre la scène et les interactions extérieurs comme des entrées clavier, souris ou des entrées spéciales. Cet objet est fait pour faciliter la gestion des différents objets de la scène et la communication entre ces objets. L'Event System gère quel objet est sélectionné, quelle gamme d'entrées sont considérés et les différentes mise à jour de la scène.
- L'objet Canvas permet l'affichage des erreurs, cet objet a été ajouté afin de pouvoir afficher du texte en jeu. Il est positionné devant le joueur mais ne peut être activé seulement à partir de Unity, quand cet objet est activé cela signifie que l'on est en DEBUG mode. Nous pouvons ainsi voir tous les messages que le programme essaye de nous envoyer et ainsi détecter les erreurs plus facilement.
- L'objet OVRPlayerControler est l'objet le plus complexe, nous ne rentrerons pas dans les détails mais cet objet va gérer toute la partie casque, manette lié au casque Oculus et suivi des mains du joueur. Il possède une partie de suivi de position du casque permettant de positionner le joueur dans l'espace de la scène, pour cela il a des sous objets fixe qui lui servent de repère dans l'espace. Il possède ensuite d'autres sous objets fixe afin de pouvoir suivre le mouvement des manettes ou des mains à l'aide des caméras qu'il possède.
- L'objet GameControl regroupe les manettes NES de base que l'on utilise dans la configuration par défaut. Il y a trois sous objets, soit trois manettes. Ces trois manettes ont une seule différence leur positionnement, elles sont positionnées de telle sorte que lorsque les trois manettes sont activés on puisse avoir accès à tous les boutons sans conflit entre eux. L'objet utilise un script Game Controle Button permettant la modification du nombre de manette affiché et donnant la possibilité de changer les boutons actifs à intervalle de temps régulié.
- L'objet PadLibre est l'objet créé pour notre scénario, c'est une manette NES comme les sous objets du GameControl mais sans le design autour donnant une impression de bouton volant et nous permettant donc de déplacer n'importe où les boutons dans l'espace.
Les scripts utilisés
- ActiveControllerCollider permet l'activation des collisions des mains avec les autres objets de la scène ayant leur collision activée (Partie du projet où l'on ne veut plus utiliser les manettes de l'Oculus mais plus que les mains)
- ChangeScale permet d'agrandir ou réduire l'objet sur un input particulier
- ClickOnButtonMenu est un script pas utilisé mais fait dans le but de pouvoir viser avec la manette de l'Oculus et sélectionner certain boutons, ici les boutons du menu
- CollisionScript fait toutes les commandes nécessaires lors de la rencontre entre la manette et les boutons, ce script rend le bouton un petit peu plus foncé et fait vibrer la manette lors de la collision afin de pouvoir voir que l'on touche le bouton. De plus il change des variables afin que le programme sache quel bouton est appuyé et puisse l'envoyer à la raspberry.
- ControllerXbox permet de détecter les entrées du contrôleur xbox afficher sur le site web, les transforme en commande et les envoie à la raspberry.
- Cube est un script s'occupant de la partie du projet sans les manettes, il permet de créer la représentation des mains du joueur
- GameControleButton a pour but de gérer l'objet GameControl et les trois manettes qui vont avec, il permet d'activer de zéro à trois manettes tout en les positionnant du mieux possible afin qu'elles soit le plus centré possible. De plus lorsque plusieurs manettes sont activées le script possède un système de changement de bouton actif afin que les boutons actifs change en permanence, par défaut c'est toutes les 30 secondes.
- Handcollider est un script s'occupant de la partie du projet sans les manettes, il permet de créer les collisions entre les mains du joueur et les différents objets de la scène.
- HandInteraction est un script s'occupant de la partie du projet sans les manettes, il permet de transformer certain mouvement de doigt ou position de main en commande comme ouvrir le menu ou autre
- Lz4DecoderStream permet de coder et décoder les messages que nous envoyons et recevons de la raspberry.
- OculusController permet de détecter les entrées du contrôleur de l'Oculus, les transforme en commande et les envoie à la raspberry.
- QuitGame permet de fermer l'application proprement.
- rotationScreen est un script créé dans le but de faire tourner l'écran par la suite mais pas utilisé pour le moment.
- TCPClient est un script fonctionnant en collaboration avec le script TCPScreen afin de mettre à jour l'affichage du jeu produit par la raspberry.
- TCPCommande permettant de faire la liaison entre les collisions entre les manettes et les boutons et les commandes envoyé à la raspberry pour manipuler le jeu.
- TCPConfig permet la configuration de notre scène (de l'environnement de jeu), celui ci lit les messages en attendant une configuration dans un thread, une fois que cette configuration est arrivée il décortique l'information et lance les fonctions nécessaires et change les variables nécessaires afin d'appliquer la nouvelle configuration reçu.
- TCPScreen permet de recevoir les messages en provenance de la rapsberry, les décode à l'aide du script Lz4DecoderStream et les affiches ensuite. La réception des messages est fait dans un thread comme toutes les réceptions de message.
- VibrationManager permet de faire vibrer les manettes lorsque la fonction est appelée.
Brainstorming
Première réunion avec tous les membres du projet afin de trouver les meilleurs scénarios et les meilleurs paramètre à mettre en place pendant notre PFE.
Au cours de cette réunion nous avons pu récolter des idées de scénario de toute l'équipe pouvant convenir à notre projet. Nous avons pu en ressortir deux idées de scénario qui apporterait un plus à notre scénario. La première est un scénario dans lequel nous pouvons déplacer à souhait les différents boutons que compose la manette NES afin de pouvoir changer la difficulté de l'exercice pendant celui-ci et pouvoir adapter l'exercice aux plus de patients possible. La seconde idée a pour but de faire marcher le patient, pour ceci nous avons pensé à faire bouger l'écran afin que le patient soit obligé de se déplacer pour pouvoir continuer à voir l'écran correctement et donc jouer. Ce scénario pose plusieurs problèmes, si nous faisons bouger l'écran et que le patient bouge, nous devons faire bouger la manette NES matérialisé dans le casque. Cependant, faire bouger la manette NES sans aucune concordance avec les déplacements du patient rendrait l'utilisation de la manette très difficile. Nous avons donc décidé d'accrocher la manette NES à la caméra et ainsi toujours l'avoir devant les yeux. Le second problème vient du déplacement, si le patient se déplace avec le casque sur les yeux il ne pourra pas voir les obstacles qui l'entoure. Pour cela la seule solution est un bon paramétrage du guardian (zone de jeu, en dehors de cette zone de jeu l'image s'arrête et le casque arrête d'afficher le jeu pour afficher ce qui entoure le patient grâce aux caméras de l'Oculus) ou une surveillance des médecins. Pour ce second problème nous pouvons limiter les risques d'accident en définissant les déplacements de l'écran et toujours le faire revenir à sa position initiale et donc faire des aller-retour. A la fin de cette réunion nous avons pu parler de ce qui était faisable dans notre temps imparti et avec l'équipe nous en avons déduit que le second scénario était trop dur et trop long à réaliser en intégralité avec le peu de temps que nous disposons au vu de notre moindre connaissance des différents langages de programmation de Unity et du projet en général. Cependant il serait bien que l'on en étudie la faisabilité pour les personnes qui continuerons de travailler sur le projet. Nous avons ainsi été assigner au premier scénario qui est le plus intéressant en termes de résultat et apparemment le moins difficile à faire.
Le premier scénario
Suite au brainstorming, notre tuteur nous a lancé sur notre premier scénario qui a pour avantage de pouvoir convenir au plus de patient possible et surtout à la majorité des pathologies nécessitant une rééducation du haut du corps. Ce scénario est en réalité une configuration particulière et très polyvalente, le but est de délier les boutons de la manette NES de leur support afin de pouvoir les placer tout autour du joueur. Ainsi le but est de pouvoir changer le placement de tous les boutons quand on le souhaite et pouvoir les placer là où on le souhaite à l'aide de la page web.
La partie web
Du côté du serveur web nous devons créer deux affichages gérant les boutons afin que l'utilisateur puisse bouger les boutons en 3D et ainsi augmenter ou diminuer la difficulté de l'exercice en temps réel. Ceci pose plusieurs problèmes :
Partie Unity
Du côté du casque de réalité virtuel nous devons créer un nouvel objet manette NES couplé à un nouveau script afin de placer les boutons en fonction de la configuration que nous recevons. Pour commencer nous avons dû nous entendre sur la formation du message json permettant de transférer la configuration entre la raspberry et le casque. Nous avons choisi de créer un sous fichier json pour chaque bouton composé de ses trois coordonnées. Notre choix de créer un nouvel objet indépendant de tous les autres objets de la scène a pour but de garder le projet le plus propre possible. Comme nous sommes en coopération avec l'équipe mint nous avons préféré modifier le moins possible les fichiers de base afin de pouvoir mieux identifier ce que l'on a fait et permettre à l'équipe de pouvoir utiliser les fonctionnalités que nous allons rajouter le plus pertinemment possible. Dans ce projet nous sommes nouveaux et ne connaissons pas toutes les subtilités de celui-ci, il est donc plus prudent de faire tout à part et ajouter notre travail au projet une fois que tout fonctionne et aura était validé par notre tuteur.
Une fois cela définit nous avons dû apprendre comment manipuler un fichier json en C#, récupérer les informations que l'on souhaite. Cela nous a pris quelque temps car avec le programme déjà en place nous avons pu voir comment récupérer les éléments basiques d'un fichier json et les traiter. Cependant le fait que nous aillons choisi d'avoir mis des objets json pour chaque bouton cela impliqué une nouvelle méthode de lecture car celle utilisée dans ce projet ne fonctionner pas. Nous avons remarqué que lors de l'appel à la fonction affichage, l'affichage se fait correctement mais lorsque nous essayons de manipuler les objets le compilateur nous produit des erreurs. Nous avons donc choisi de reproduire une partie de la fonction d'affichage, la partie cast nous permettant alors de manipuler les sous objets comme des strings pour pouvoir enfin récupérer les données qui nous on était envoyé. Nous envoyons ensuite la configuration des boutons dans le casque.
Afin que tous cela fonctionne nous avons dû modifier le fichier de configuration qui est l'une des pièces maîtresse du projet. Nous avons fait en sorte que cette fonction ne soit pas la prioritaire et que la fonction prioritaire soit la manette NES totalement prédéfinie.
La modification du fichier de configuration n'a était faite qu'une fois le test fini c'est à dire que pour tester notre script nous l'avons rendu complétement indépendant avec une mise à jour lors d'une entrée clavier sur un fichier qui était directement stocké sur l'ordinateur. Le fait de travailler sous Unity avec un matériel de ce type et une dépendance à la raspberry nous a obligé à nous adapter et découvrir de nouvelle fonctionnalité du logiciel Unity. Ainsi après avoir testé tout cela au fur et à mesure grâce à Unity nous avons pu tester cela avec la configuration venant directement de la raspberry. (Ne pas oublier d'enlever tous ce qui a permis de faire les tests sur le simulateur de Unity et ajouter les liaisons avec le fichier de configuration.)
semaine 3
Le premier scénario (suite)
La phase de test et de debug
Pour cette troisième semaine, nous reprenons notre travaille en cours, maintenant que les parties programmation web et programmation Unity pour les boutons sont fini nous pouvons tester le tout. Lors de ce test nous avons découvert un nouveau type de bug qui entraînait la fin de l'update du côté de l'oculus. Dans un projet Unity tous les scripts qui sont liés à un objet Unity possède une fonction Start qui se lance lors du lancement du projet et une fonction Update permettant la mise à jour des images, elle est donc appelée à chaque image formée par l'Oculus. Nous faisons tourner l'Oculus sur une base de 60 images par seconde ce qui produit un appelle de la fonction Update 120 fois par seconde car l'oculus fonctionne avec deux écrans, un par oeil.
Ainsi lorsque le programme s'arrête pendant l'update, le jeu continuera de fonctionner et ignorera seulement la fin de la fonction Update et la rappellera au prochain rafraîchissement. De plus lorsque nous transférons le projet Unity sur le casque de réalité virtuel celui-ci devient indépendant donc nous n'avons aucune façon conventionnelle de trouver les erreurs. Nous avons alors dû créer un objet dans la scène unity affichant des messages, pour aller avec cela nous avons dû créer des logs afin de trouver pourquoi l'Update ne se faisait pas.
Nous avons enfin découvert notre problème, notre lecture de message et donc de fichier json était fait sans aucune protection. C'est à dire qu'au moindre problème de lecture de message, de formation de message ou de formation du fichier de configuration json. Pour que notre fonction Update fonctionne ce message devait être parfait et toutes les options devait être renseigné sinon la lecture du json formé à partir du message provoqué une exception. Nous avons ainsi ajouté des protections pour toutes les lectures d'options implémenté et utilisé. Pour cela nous avons dû choisir une configuration par défaut, la configuration choisie est la configuration simple d'une manette formé sans aucune modification. Pour cela nous avons tout d'abord initialiser toutes les options afin de répondre à la configuration par défaut. Ensuite pour chaque option nous vérifions que l'option est présente dans le json avant d'essayer de la lire. Une fois lu nous mettons la priorité sur la configuration par défaut en cas de conflit entre les différentes options. Nous faisons la même chose pour la lecture des boutons si l'option padLibre est activé, tous les boutons ont une place prédéfinie qu'ils prennent en cas de manque d'information ou de problème de lecture.
Structure finale des scripts
Finalement pour ce scénario nous avons créé un seul script permettant de gérer le nouvel objet que nous avons créé. Ce script reste en relation avec les scripts principaux du projet, comme le script TCPConfig que nous avons modifié pour qu'il puisse continuer de fonctionner en cas d'erreur dans le message envoyé mais aussi les scripts de collision, de vibration et de commande. Ainsi notre script nommé ButtonChange comporte les deux fonctions de base qui sont start() et update avec start qui est lancé au lancement du programme et update qui se lance à chaque frame. Ces deux fonctions sont utilisés uniquement lors des tests dans le simulateur d'Unity. Nous avons ensuite les deux fonctions suivantes : ChangeButtonPosition et getKey. La fonction getKey permet d’interagir avec les boutons et donc envoyer les bonnes instructions au jeu. Nous arrivons enfin à notre fonction ChangeButtonPosition qui prend en entré le dictionnaire fait directement à partir du json reçu en message. Cette fonction commence par placer notre support invisible afin de pouvoir mieux se repérer dans l'espace par la suite. Elle continue par vérifier que l'emplacement de tous les boutons sont bien présent dans la configuration en créant deux liste, une pour les boutons présents et une seconde pour les boutons manquants. Une fois cela fait nous arrivons dans le coeur de la fonction, nous commençons avec une boucle sur tous les boutons que l'objet possède, nous vérifions qu'ils sont bien présents dans le json lu. Si ils sont présents nous les activons puis nous récupérons le sous-fichier json grâce à la méthode d'affichage expliqué plus haut. Nous continuons par vérifier puis récupérer leur paramètres avant de les assigner aux boutons. Les paramètres absents sont remplacés par les paramètres par défaut stockés dans le tableau base_pos. Si ils ne sont pas présents dans le fichier de configuration nous les configurons directement par rapport au tableau base_pos.
Le second scénario
Ce second scénario comme on a pu le voir précédemment consiste à faire bouger l'écran (l'éloigner et le rapprocher) afin de faire marcher le patient tout en gardant la manette NES matérialisé juste devant le joueur. Dans un premier temps nous devons en étudier la faisabilité et le coder si le temps nous le permet.
Faisabilité
Nous devons commencer par identifier les fonctions que ce script doit avoir avant de nous lancer.
Afin de créer ce second scénario le script doit être capable de faire bouger l'écran de façon dynamique, c'est à dire que l'écran ne peut pas disparaître d'un point pour réapparaître 5m plus loin. Nous devons pouvoir gérer la vitesse de mouvement de l'écran et limiter ses déplacements selon l'envie du praticien et donc pouvoir rendre cela configurable par le praticien.
Du côté du pad (la manette NES dans le jeu) nous devons faire en sorte qu’elle suive le joueur tout au long de la partie pour ainsi pouvoir laisser le joueur libre de ses mouvements.
Pour commencer nous reprenons ce que l'on a pu voir précédemment pendant la découverte du code et le développement du premier scénario et nous arrivons sur des fonctions de positionnement et de rotation avec la fonction de positionnement utilisé pour positionner les boutons et la fonction de rotation qui est utilisé pour positionner les manettes dans le scénario qui permettait de choisir le nombre de manette NES nous voulons pour jouer. Ce scénario était déjà présent à notre arrivée, celui-ci nous a beaucoup aidé dans la manipulation d'objet et dans la découverte des fonctions de base dédié aux objets Unity et à la communication entre les différents objets et script.
Ainsi avec cela nous avons pu voir que ce second scénario était possible mais vraiment peu pratique pour faire bouger l'écran. En ce qui concernés le fait de fixer la manette devant le joueur, la fonction pour déplacer l'objet est parfaite pour nous permettre de garder la manette devant nous, car nous ne savons pas comment le joueur va bouger, s’il va reculer ou tourner la tête ou se baisser. Elle était parfaite à condition de pouvoir avoir la position du casque à chaque frame et ainsi modifier la position de la manette à chaque frame.
Nous continuons alors par chercher comment trouver la position du joueur, en réalité dans un projet Unity pour les casques de réalité virtuelle ne matérialise pas le corps de la personne qui joue et donc n'a aucun repère sur la position de son corps. Cependant nous avons un objet caméra qui représente le casque dans la scène et donc la tête du joueur. Nous avons donc besoin de récupérer les positions de cet objet Unity et de les utiliser afin de placer le pad. Après des recherches sur la documentation Unity et le forum Unity nous avons pu trouver la fonction Camera.main.transform nous donnant l'accès à l'objet camera. Nous pouvons donc avoir accès à toutes ses variables de position comme si c'était un objet standard de la scène. Cependant nous ne pouvons pas modifier sa position ou faire toute action de ce genre, pour bouger la vision nous devons créer un objet secondaire caméra le configurer avant de l'assigner en caméra principale et ensuite l'assigner en caméra principale pour que le joueur change de point de vue. Ceci est souvent utilisé dans les jeux modernes mais ce n'est pas notre cible ici.
Maintenant que nous avons vu qu'il est possible de garder la manette fixée à la vision du joueur, les fonctions sont disponible il suffira d'un peu de mathématique pour la garder à la place souhaitée. Nous pouvons nous concentrer sur le déplacement de l'écran, c'est un objet comme les autres, nous pouvons donc le déplacer à chaque frame un tout petit peu et créer un déplacement rectiligne selon des options définie lors de la configuration. Cependant nous avons tout de même cherché sur leur documentation si il n'y avait pas quelque chose de plus facile et plus pratique à utiliser et nous avons trouvé la fonction Translate. Cette fonction permet de bouger un objet de façon rectiligne selon un vecteur défini.
Nous pouvons maintenant affirmer que le second scénario est faisable et pourrait être intégré au projet sans grande difficulté.
semaine 4
Le second scénario (suite)
La semaine dernière nous avons pu voir que ce second scénario était faisable, il nous reste du temps donc nous allons pouvoir commencer son développement et l'implémenter dans le projet. Ce second scénario comporte deux parties, celle de la manette devant suivre le joueur et celle de l'écran devant bouger dans l'espace. Nous allons commencer par la partie la plus mathématique, le suivi de la manette.
Suivi du Pad
Pour cette première partie, nous voulons que le pad nous suive et reste devant nous, nous avons commencé en pensant que le pad devait rester face à l'écran donc nous n'avons pas fait attention aux rotations et avons fixé le pad afin qu'il reste face à l'écran. Pour cela nous avons dû commencer par récupérer la position de la caméra et simplement assigner à l'axe x la valeur de zéro afin que l'objet reste en face de la caméra, à l'axe y la valeur de -0.5 afin de garder l'objet 50cm en dessous de la caméra et ainsi avoir la manette un peu au-dessus du bassin et pour l'axe z nous avons assigné à l'objet la valeur de 0.4 qui est la distance entre le corps et le centre de l'objet pour ainsi avoir la manette devant ses yeux.
Ceci a pu être fait si facilement, car le repère utilisé est le repère de la caméra, ainsi il suffit juste de mettre à jour à chaque frame la position de l'objet afin que celui-ci bouge avec la caméra (la vision du joueur). Ceci était fait à chaque appelle de fonction Update et donc à chaque image créée pour le jeu(frame).
Ceci était bien mais trop simpliste et pas assez pratique, ce que souhaitait notre tuteur était une manette qui nous suive partout et soit toujours orienté vers le joueur. Nous avons donc continué le développement en prenant compte l'orientation du casque dans l'espace. Le défi était de toujours avoir la manette face à nous, conserver toujours la même distance et toujours l'avoir orienté vers nous.
Nous avons donc utilisé la trigonométrie en récupérant les différentes orientations de la caméra et en conservant la distance entre la position du joueur et la position de la manette. Une fois que cela a était fait nous nous sommes concentré sur la partie rotation de la manette à l'aide de la fonction rotate. Cette partie n'a pas été facile, à force de manipulation avec cette fonction, nous avons pu enfin trouver exactement comment elle fonctionnait, nous avons modifié notre code en conséquence. Pour arriver aux demande de notre tuteur nous avons alors regarder à chaque rafraichissement d'image comment le casque avait bougé et nous bougeons la manette en conséquence, ainsi la manette est déplacé à chaque frame. Nous la voyons donc ce déplacer en continue dans la vision du casque.
Mettre en mouvement l'écran
Pour cette seconde partie, nous n'avons pas fait très compliqué, nous avons créé une fonction de mouvement prenant en paramètre la vitesse et la direction que l'on voulais donner à l'écran et il nous suffi de l'appeler à chaque image. Pour éviter que l'écran ne se déplace à l'infinie nous avons ajouter une fonction de protection prenant en entré des limites ou prenant les limites par défaut. Nous nous somme limité à des mouvement basique de va et viens mais selon ce que le tuteur voudras faire dans le futur il suffira de modifier la direction afin de répondre aux différentes idées que l'équipe pourrait avoir par la suite. La fonction limite modifie déjà la direction en l'inversent afin de faire des aller retour mais nous pouvons imaginer une autre fonction ou la fonction de limite modifiant en continue la direction en fonction de certain paramètres et ainsi créer des mouvements plus complexes.
Les tests
Maintenant que nous faisons bouger l'écran et que la manette nous suit nous pouvons faire les tests en condition réel. Nous avons commencer par bien définir le guardian afin de ne pas risquer de se faire mal et nous avons lancer le programme. Nous avons très vite remarqué que la manette virtuelle était bien trop grande, quand on voulait atteindre les boutons sur les côté on tourné la tête afin de les voir mais la manette partait avec donc très dur à manipuler.
Nous avons donc décider de réduire le pas dans son entièreté lors de l'activation de la suivi et le remettre à taille normal lors de la désactivation. Cela marchais très bien mais cela rendait les boutons plus petits ce que notre tuteur n'apprécier pas trop. Nous avons alors trouver une autre solution qui a était de déplacer les boutons vers le milieu de la manette en fonction de leur positionnement. C'est à dire que plus le boutons était sur le côté et plus il était ramené vers le centre et inversement, plus il était vers le centre moins il bougeait. Cette solution est très bonne, elle permet au joueur de cliquer sur les boutons sans bouger la tête et ainsi ne pas avoir de problèmes pour viser le bouton. Cependant nous avons laissé la première solution en commentaire afin de donner la possibilité à notre tuteur de changer de méthode ou de combiner les deux méthodes dans le futur. Sans oublier que cela pourra être utiliser par la suite dans de nouveau scénario ou juste si nous voulons ajouter la possibilité de changer la taille de la manette ou la taille des boutons.
Les tests était maintenant fini et tout marché très bien et nous avons pu voir que d'autre scénario était possible et pouvait dériver de notre travail. Cependant maintenant que les deux scénarios sont fait nous pouvons passer à la deuxième partie du projet que l'on devait faire si l'on avait le temps.
Semaine 5
Maintenant que les deux scénarios qui nous avait était demandé de faire son fini nous attaquons à la seconde partie du projet qui concerne une librairie créé par l'équipe MINT, la librairie Gina.
La librairie Gina
La librairie Gina à pour but de créer des événements à partir de mouvement, pour cela elle se découpe en trois bloc. Le premier bloc est le décodeur, il prend en entrée un flux d'information brut venant d'un matériel quelconque (une kinect, une sourie, le positionnement du doigt sur une tablette et d'autre) et le traduit en information utilisable par les autres bloc. Ce bloc permet d'uniformiser l'information afin de limiter le nombre de bloc suivant.
Le second bloc est un filtre, il va permettre de filtrer les mouvement entrant afin de supprimer les tremblements et autre défaut que peux avoir l'information afin d'avoir des mouvements les plus propres et les plus facilement exploitable possible.
Le troisième et dernier bloc permet de créer des évènements à partir des informations qu'il reçoit et ainsi interagir avec le système.
Cette librairie est très complètes et très pratique, cependant pour qu'un objet soit utilisable par la librairie, elle doit posséder le premier bloc permettant de convertir l'information brut de l'objet en information exploitable par les autres bloc de la librairie. Malheureusement le bloc pour android n'était pas implémenté. Cette librairie est donc inutilisable pour notre projet sans la modifier. Sous les conseils du développeur de la librairie et de notre tuteur nous n'allons pas créer le bloc de traduction et nous allons donc utiliser les fonctions proposer directement par Oculus et Unity pour arriver à jouer juste à partir des mouvements de la manette de l'Oculus et donc supprimer la manette virtuel.
Contrôle par mouvement
Comme la librairie Gina est trop difficilement utilisable et déconseillé vu que nous connaissons déjà Unity et ses différentes interactions avec le casque de réalité virtuel. Nous nous lançons alors dans la recherche d'algorithme pouvant répondre à de la reconnaissance de paterne et à la recherche des différentes fonctions intéressantes.
Nous commençons par regarder qu'elle structure pourrait avoir notre code. Nous avons choisie d'utiliser la même structure que la librairie Gina, une fonction qui détecte récupère les mouvements, une fonction qui traite les mouvements et une fonction qui traduit le mouvement en évènement afin d'envoyer les informations à la raspberry (le jeu).
Récupération des données
Pour la fonction qui récupère les mouvements nous avons commencé par chercher quand et comment récupérer les mouvements afin que cela soit le plus rapide possible et le plus efficace possible. Afin d'éviter la surcharge du flux de donné et surtout pour éviter que des mouvements non souhaité soit pris en compte par le jeu, nous avons décider de prendre en compte les mouvements des manettes uniquement quand le joueur appui sur la gâchette. Nous avons donc du chercher les différentes méthodes permettant de récupérer la position des manettes et l'état des boutons de la manette, en particulier l'état de la gâchette. Nous avons donc les méthodes suivantes :
- Input.GetAxis("Oculus_CrossPlatform_PrimaryIndexTrigger") nous donnant la puissance de pression sur la gâchette gauche.
- Input.GetAxis("Oculus_CrossPlatform_SecondaryIndexTrigger") nous donnant la puissance de pression sur la gâchette droite.
- OVRInput.GetLocalControllerPosition(OVRInput.Controller.LTouch) nous donnant la position de la manette gauche.
- OVRInput.GetLocalControllerPosition(OVRInput.Controller.RTouch) nous donnant la position de la manette droite.
Nous avons maintenant les fonctions principales nous permettant de gérer la récupération d'informations, nous stockons alors tout ça dans une liste dont nous limitons la capacité à 60*2*2, 60 pour le nombre de rafraichissement par seconde, 2 car il y a deux écrans et le second 2 car on veut que ça enregistre sur maximum deux secondes. Une fois que la position a était enregistré nous passons la liste à travers le filtre 1€.
Traitement des données
Après avoir créé les listes et les avoir filtré nous passons au traitement de l'information. Pour cette partie nous devons récupérer quel mouvement est fait par l'utilisateur mais aussi délimiter les mouvement et ne pas confondre une pose d'enregistrement avec un mouvement. Pour la première problématique, récupérer le mouvement fait nous avons simplement créé 6 variable (2 par axes) afin d'avoir la distance parcouru selon les trois axes et leur direction.
Pour la seconde problématique de délimitation de mouvement nous avons tout d'abord choisie de regarder le déplacement entre les deux derniers points enregistré et considérer les points précédents comme faisant partie d'un seul et même mouvement à condition que les valeurs ne changent pas trop. Cependant ceci était beaucoup trop imprécis, le changement de vitesse de déplacement pouvait induire en erreur et le déplacement ne s'adapter pas, c'est à dire que le corps humain arrive très peu à faire des mouvements rectiligne sans faire attention rendant alors très difficile la manipulation. Nous avons alors penser à une autre méthode consistant à normaliser les mouvements et ainsi traiter des vecteurs et non des distances que l'on fait évoluer avec le temps. Ceci donne alors plus de sens à la limite changement, nous pouvons alors parler de pourcentage de similarité ou de changement et ainsi choisir la précision que nous voulons. Afin d'optimiser cette différentiation de mouvement, nous avons d'abord une comparaison entre le vecteur de référence et le vecteur étudier nous donnant alors un pourcentage de changement. Si ce changement est trop grand nous arrêtons la détection et passons à l'interprétation des données trouvé. Sinon nous modifions notre vecteur de base en faisant la moyenne des vecteurs du mouvement et nous ajoutons les valeurs du dernier déplacement aux 6 valeurs de référence des axes.
La troisième problématique est venu une fois que tout est codé, pendant la parti il suffisait de finir un mouvement avec le bras tendu et commencer un autre mouvement avec le bras plié pour que le jeu en déduise une action et envoie à la raspberry l'action. Ceci entrainait plusieurs bug du style lorsqu'un mouvement vers la gauche était fait, le mouvement suivant envoyait au minimum un mouvement vers la droite entraînant ainsi pour le jeu Tetris l'impossibilité de mettre les pièces collé au mur. Donc pour cette troisième problématique nous avons pensé à plusieurs solution, la première serait de nettoyer les listes et ainsi reprendre comme si on avait jamais appuyer sur les gâchettes mais ceci nous oblige à avoir deux variables globales de plus pour savoir quand nettoyer les listes et ne pas les nettoyer en boucle lorsque les gâchettes ne sont pas appuyer. De plus si le matériel est en fin de vie ou si la gâchette ou l'envoie d'information bug pendant une frame, on ne veux pas perdre toute les informations récolté avant la perte de signal. Ainsi on arrive à la seconde solution qui est d'instauré un déplacement maximal entre deux frames. Nous partons du principe que nous pouvons déplacer la manette à très grandes vitesse et que nous avons 120 frames par seconde (soit 120 prise de mesures par seconde), nous pouvons alors établir un seuil de modification au delà duquel le logiciel considérera le mouvement comme un nouveau mouvement. Nous avons définie cette limite de changement à 0.05 soit 5cm ce qui correspond à une vitesse supérieur à 6 mètres par seconde ou 3 mètres par seconde si on a perdu une mesure. Cette vitesse est facilement modifiable mais impossible à dépasser pendant le jeu. Nous avons ainsi supprimer le problèmes ce qui évite tout retour en arrière et augmente la précision de la détection.
Interprétation des données
Maintenant que l'on a nos 6 variables indiquant tous les déplacements au cours du mouvement nous pouvons les interpréter et ainsi envoyer les commandes correspondantes au jeu (à la raspberry). Nous avons alors les données du mouvement en cours assez détaillé, à partir de ceci nous pouvons détecter tous les mouvements de type rectiligne. Ainsi nous avons créé une fonctions prenant en comptes les 6 variables et vérifie qu'il y a eu assez de mouvement sur la variable passer dans la première entrée et que le reste des variables n'est pas trop élevé. Cela signifie que nous voulons un mouvement dans la direction désigné par la première variable sans qu'il y ai eu trop de perturbation dans les autres direction. Nous avons laissé la possibilité de modifier facilement les limites et d'ajouter des fonctions d'interprétation.
Les tests
Tous ceci est le résultat de beaucoup de test nous ayant permis de régler les variables de changement afin de plus facilement délimiter les mouvements, régler les limites d'interprétation afin d'éviter de faire des gestes trop amples. De plus ces tests nous on permis de nous rendre compte du troisième problème du traitement des données. De plus, au début nous avons pas penser à ajouter un filtre mais avec les tests et le fait que les utilisateurs seront des personnes en rééducation donc des personnes n'ayant pas pleinement la possibilité de faire des gestes droits, l'utilisation d'un filtre était obligatoire. Cependant l'utilisation de ce filtre entraine un lissage des positions et donc rend notre protection contre les saut c'est à dire entre la fin d'un mouvement et le début du suivant, lorsqu'on lâche la gâchette avec la manette à un certain endroit et que nous bougeons la manette pour commencer un autre mouvement.
La troisième problématique est de nouveau d'actualité, nous avons donc étudier plus en profondeur le code du filtre 1€ développé par cristal afin de trouver une solution pour régler se problème et ainsi réinitialiser le filtre lorsque le mouvement est trop grand. Cependant après plusieurs test nous ne sommes pas arriver à résoudre ce problème de cette façon. De plus le temps nous manque et le filtre ne comporte pas de fonction de destruction, nous supposons alors que le garbage colector fonctionne correctement sur le filtre. Avec cette supposition nous créons alors un nouveau filtre des que la gâchette est relâché mais ceci pourrait créer une perte de mémoire donc pas optimale, ceci aura besoin d'être modifié par la suite.
Semaine 6
Maintenant que les deux scénarios était fait et que nous avons fini le contrôle par le mouvement des manettes nous avons fait beaucoup de test et de combinaison afin de chercher les bugs qui pourrait survenir. C'est ainsi que nous avons du régler les différents scénarios afin que ceux ci puissent s'exécuter proprement et que l'on puisse changer de scénario sans que tout bug et que l'on soit obligé de relancer l'application.
Le premier était qu'il nous était impossible d'accéder au second scénario ou le lancement de celui ci était très approximatif et l'écran passait derrière le mur. Nous avons donc réussi à régler le problème qui venait de la configuration qui oublié d'activer le gamepad utilisé dans ce scénario et désactiver le mur. Une fois cela fait nous avons remarqué que nous ne pouvons plus sortir du scénario. Nous avons fini par remarquer une erreur dans notre configuration une nouvelle fois, cette fois ci la partie de désactivation n'était pas atteinte à cause d'un certain enchaînement de commande. Nous avons donc réglé cela ainsi que la fonction de fin de suivi dans laquelle nous avons oublié de rétablir les positions initial des boutons.
Suite à tous ces tests nous sommes satisfait de notre travail et de son bon fonctionnement. La plus part de notre travaille a était fait dans des nouveaux scripts afin de ne pas gêner le projet et afin de les rendre le plus facilement réutilisable par la suite. Que ce soit complètement dans les scénarios que nous avons élaboré ou partiellement dans des scénarios plus développé.
Sur demande de notre tuteur nous arrêtons de développer et d'améliorer ce que l'on a fait pour combinais notre travaille avec le travaille qu'il a effectué sur le projet pendant ces 6 semaines. Cependant avec le développement nous avons pu voir plusieurs scénario ou option qui pourrait être implémenter pour la suite du projet.
Potentiel amélioration
Pour le premier scénario, nous pouvons l'améliorer en ajoutant une options permettant de choisir la taille de chaque boutons en plus de leur position. De plus un script que nous n'avons pas utilisé durant notre PFE est déjà présent dans le projet. Cette option pourrait être ajouté par la suite selon les envies du client ou de notre tuteur. Cependant ceci pourrait rendre la configuration trop lourde pour un médecin et donc faire perdre trop de temps, il faudrait donc voir avec eux.
Pour le second scénario, nous pouvons créer une fonction permettant de faire bouger l'écran selon un paterne prédéfinie ou configurer par le médecin avec la manette. Ce qui pourrait être imaginé c'est que le médecin prenne une des manette de l'oculus pour définir le déplacement que l'écran devra faire en appuyant sur un bouton. Le script aurait à transformer les positions en une liste de vecteur puis faire bouger l'écran soit en faisant des aller retour ou des boucles selon si le déplacement fait par le médecin commence en un point et fini au même point ou pas. Ainsi pour cela il faudrait une fonction pour traduire les positions en une liste de vecteurs, ceci peut s'appuyer sur la normalisation et le filtre utilisé dans le script de détection de mouvement. Il faudrait ensuite une fonction prenant une liste de vecteur et une vitesse afin de déplacer l'écran, ceci peut s'appuyer sur la fonction permettant déjà de bouger l'écran avec un flag permettant de dire si on fait des aller retours ou des boucles et une variable globale permettant de se déplacer dans la liste.
De plus nous ne savons pas si cela est possible mais pour la sécurité du patient et pouvoir permettre au médecin de lancer le patient dans son exercice et pouvoir partir voir d'autre patient nous pouvons imaginer un système de détection des obstacles et les modéliser mais nous ne savons pas combien de temps ça pourrait prendre si c'est faisable.
Pour la gestion des mouvements, il y a le problème du filtre à régler pour commencer. Ensuite notre code laisse la possibilité de différencier les deux manettes grâce à une variable nommé main ce qui pourrait mené à quelque optimisation ou amélioration selon les envies du tuteur ou les demandes des médecins. De plus nous pouvons imaginer de nouvelle détection de mouvement plus complexe que de simple mouvement rectiligne que nous avons pu implémenter la semaine dernière. Pour cela nous pouvons imaginer une fonction connaissant tous les mouvements implémenter, prenant le mouvement en cours du joueur et éliminant les différentes possibilité de correspondance jusqu'à ce que la correspondance soit parfaite avec un des mouvements implémenter. Ceci permettrait d'éliminer le problème de ne pas avoir assez de mouvement implémenter pour y faire correspondre les 8 boutons de la manette NES nécessaire pour jouer. Une autre amélioration est possible avec la détection de mouvement, c'est le fait d'appuyer sur un bouton afin qu'une fois le mouvement détecter le jeu considère que l'on reste appuyer sur le bouton permettant ainsi de maintenir un bouton enfoncer. Ce qui est impossible à faire sur plus de quelques secondes avec la détection des mouvements normale car lorsque nous arrêtons de bouger notre main avec la manette nous tremblons, le joueur ne sait pas garder sa main stable. De plus la détection peu créer une erreur infime mais suffisante pour que le vecteur créé ne corresponde pas au mouvement précédent. Ainsi le script considère que nous avons commencer un nouveau mouvement et enverra la commande fin de mouvement.