diff --git a/rapport/rapport.tex b/rapport/rapport.tex index c9a74b6..5a80e28 100644 --- a/rapport/rapport.tex +++ b/rapport/rapport.tex @@ -45,6 +45,9 @@ \newcommand{\hsc}[1]{{\Large\MakeUppercase{#1}}} \newcommand{\hscs}[1]{{\footnotesize\MakeUppercase{#1}}} +\newcommand{\threejs}{\href{http://threejs.org/}{Three.js}} +\newcommand{\socketio}{\href{http://socket.io/}{Socket.IO}} + \newcommand{\namedparagraph}[1]{\paragraph{#1}\mbox{}\\} \newcommand{\tikzvline}[2]{ @@ -94,20 +97,22 @@ \end{titlepage} \normalsize -\newpage + \tableofcontents \newpage -\section*{Introduction} -\section{Choix des technologies et prise en main} +\part*{Introduction} +\newpage + +\part{Choix des technologies et prise en main} \paragraph{} La première phase de stage était de choisir les technologies qui allaient être utilisées par la suite. Nous cherchions des technologies permettant la visualisation 3D sur un navigateur web afin de pouvoir faire une étude utilisateur simplement. -\subsection{Côté client} +\section{Côté client} \paragraph{} Pour le côté client, il y avait plusieurs possibilités : \begin{itemize} @@ -121,10 +126,15 @@ Pour le côté client, il y avait plusieurs possibilités : \paragraph{} La plupart des moteurs graphiques exportant vers JavaScript sont putôt lourd à prendre en main, et nous voulions garder des solutions simples, c'est pourquoi -nous avons utilisé une librairie libre nommée Three.js permettant une +nous avons utilisé une librairie libre nommée \threejs permettant une utilisation facile de WebGL. -\subsection{Côté serveur} +\paragraph{} +Pour des raisons de simplicité, nous avons décidé de développer le code client +pour Google Chrome et Firefox, les autres navigateurs ne sont donc pas +(officiellement) supportés. + +\section{Côté serveur} \paragraph{} Dans un premier temps, seul le côté client était pris en compte. Les programmes étaient écrits en JavaScript et ne nécessitaient pas de serveur. Quand les @@ -143,12 +153,12 @@ herbergé sur une petite machine de 512Mo de RAM). \paragraph{} Quand les problématiques de streaming ont commencé à apparaître, nous avons choisi la simplicité en utilisant Node.js pour le côté serveur (un serveur -écrit en JavaScript) à cause de la présence d'une librairie nommée Socket.io +écrit en JavaScript) à cause de la présence d'une librairie nommée \socketio qui s'avère très pratique pour la communication entre le client et le serveur. Pour des raisons pratiques, le serveur a été herbergé sur un cloud gratuit (OpenShift). -\subsection{Base de données} +\section{Base de données} \paragraph{} Pour le système de gestion de base de données, nous avons choisi Postgres (qui est libre et qui a largement fait ses preuves). OpenShift propose d'héberger @@ -156,7 +166,7 @@ lui-même la base de données, mais la version gratuite ne proposant qu'1 Go d'espace de stockage, nous avons préféré l'héberger nous-même. -\subsection{Développement, debug et déploiement} +\section{Développement, debug et déploiement} \paragraph{} Pour éviter d'avoir des fichiers trop longs, nous avons choisi de séparer les sources dans de nombreux fichiers de taille plus petite, et de les fusionner @@ -169,7 +179,7 @@ script : n'oublions pas que nous parlons de serveur web, et il est donc intéressant de réduire la taille des programmes de sorte à les charger plus rapidement} (effacement des commentaires et des retours à la ligne, simplifications des noms de variables et plus \footnote{en JavaScript, il est -plus rapide d'écrire \texttt{!0} pour \texttt{true} par exemple.}). Pour le +plus court d'écrire \texttt{!0} pour \texttt{true} par exemple.}). Pour le développement, on a utilisé \href{https://github.com/remy/nodemon}{nodemon} et inotify, qui permettent de relancer le serveur local lorsqu'une modification est détectée (la fusion des fichiers est donc réeffectuée). @@ -192,8 +202,9 @@ Pour nous aider au debug, nous avons utilisé \href{http://jshint.com/}{JSHint} qui nous aide à détecter les erreurs potentielles liées aux subtilités du langage. -\section{L'interface} -\subsection{Interactions élémentaires} +\newpage +\part{L'interface} +\section{Interactions élémentaires} \paragraph{} La première interface a été pensée pour être la plus simple possible. L'utilisateur contrôle une caméra qui se déplace librement dans un modèle 3D. @@ -215,13 +226,14 @@ On peut pivoter la caméra de plusieurs manières : \end{itemize} -\subsection{Les recommandations} +\section{Les recommandations} \paragraph{} Les recommandations sont là pour suggérer des points de vue à l'utilisateur. Elles permettent d'aider la navigation. Elles sont affichées sous forme d'objets 3D ajoutés à la scène. Deux affichages ont été testés. -\subsubsection{Les \emph{viewports}} + +\subsection{Les \emph{viewports}} \paragraph{} Les \emph{viewports} sont les affichages les plus simples : ils représentent une caméra, avec son centre optique et son plan image. @@ -237,7 +249,7 @@ cause de la perspective (dans cette image, il peut être difficile de savoir si le point de vue et vers le modèle ou vers nous). -\subsubsection{Les flèches} +\subsection{Les flèches} \paragraph{} Les flèches sont supposées être plus intuitives pour un utilisateur qui n'a pas l'habitude des \emph{viewports} précédemment utilisés. Plutôt que de suggérer @@ -248,8 +260,8 @@ un point de vue, elles suggèrent le mouvemement qui va mener à ce point de vue \caption{Des recommandations flèches} \end{figure} -\subsubsection{Les interactions} -\namedparagraph{Au survol} +\subsection{Les interactions} +\subsubsection{Au survol} \indent Cette fonctionnalité est inspirée des récents lecteurs video sur le web. Lorsque l'on regarde une video, on a la barre de \emph{seeking} en bas et passer le curseur sur cette barre affiche l'image de la vidéo à l'instant visé. @@ -262,7 +274,7 @@ une petite boite au voisinage du curseur. \caption{Une prévisualisation} \end{figure} -\namedparagraph{Au clic} +\subsubsection{Au clic} \indent Lors d'un clic sur une recommandation, la caméra suit un mouvement fluide jusqu'au point de vue recommandé. La trajectoire est définié par un polynôme interpolant tel que : @@ -283,7 +295,8 @@ sont bleues si elles n'ont jamais été clicées, et deviennent violettes si l'utilisateur les a déjà consommées. Ceci est fait pour qu'un utilisateur puisse savoir par où il est passé, et ce qui lui reste encore à visiter. -\subsection{Autres éléments de navigation} + +\section{Autres éléments de navigation} \paragraph{} Pour faciliter la navigation, quelques autres éléments de navigation sont présents. @@ -307,7 +320,7 @@ présents. \caption{Les différents éléments de l'interface} \end{figure} - +\newpage \paragraph{} \begin{enumerate} @@ -334,7 +347,8 @@ présents. \end{enumerate} -\section{L'étude utilisateur} +\newpage +\part{L'étude utilisateur} \paragraph{} Pour tester le comportement des utilisateurs face aux recommandations, nous avons dissimulé des pièces rouges à travers ces modèles, et nous avons demandé @@ -351,12 +365,300 @@ recommandations visent les pièces rouges, il est évident qu'il sera très faci de les trouver avec les recommandations), un système de tirage aléatoire de pièces rouges a été fait. +\section{Déroulement de l'expérience} +\subsection{Première page} +La première page présente rapidement l'expérience. Elle vérifie aussi le +navigateur : si le client est sur Google Chrome ou Firefox, un lien apparaîtra +pour passer à la suite, sinon, un message d'erreur s'affichera. + +\subsection{Identification} +Cette page nous permet d'en savoir un peu plus sur l'utilisateur : nous allons +demander l'age, le sexe et les habitudes en terme de jeux video de +l'utilisateur (nous avons considéré que la capacité des utilisateurs à manier +notre interface allait dépendre fortement de leur habitude aux jeux video). +Nous leur demandons notamment de noter leurs capacités en terme de jeux videos +(entre 1 et 5 étoiles). + +\subsection{Tutoriel} +Ensuite, nous demandons à l'utilisateur de suivre un tutoriel : c'est une sorte +de réelle expérience mais guidée. Des messages indiquant les interactions à +faire aideront l'utilisateur à s'habituer à cette interface. On y présente les +moyens de déplacer la caméra, puis les recommandations et les différents +boutons de l'interface. + + +\subsection{Les trois vraies expériences} +C'est ensuite que la \emph{vraie} partie de l'étude utilisateur commence : à 3 +reprises, l'utilisateur va se retrouver dans une scène avec certaines pièces +rouges à trouver, et un certain style de recommandations\footnote{aucune +recommandation sera considéré comme un style de recommandation, de sorte à +comparer la présence à l'absence de recommandation pour la navigation} pour +l'aider. Nous expliquerons dans la sous-section \ref{selection} comment le +choix de la scène et du style de recommandation sera fait. + \paragraph{} +Dans chacune des expériences, l'utilisateur devra chercher des pièces rouges, +en s'aidant (ou pas) des recommandations. L'expérience se terminera soit quand +l'utilisateur aura trouvé les huit pièces rouges, soit une minute après avoir +trouvé la 6\up{ème} pièce rouge. Pour éviter que l'utilisateur soit +\emph{frustré} de ne pas avoir trouvé toutes les pièces, nous n'indiquons pas +clairement le nombre de pièces qu'il a, ou qu'il lui reste à trouver. +Simplement, une \emph{vague} idée de sa progression. + +\subsection{Le \emph{feedback}} +Après avoir fini ces expériences, l'utilisateur se retrouvera sur un formulaire +lui demandant son avis quand à la difficulté de l'interface, et l'utilité des +recommandations pour se déplacer dans la scène. + +\section{Choix de la scène et du style de recommandations\label{selection}} +\paragraph{} +Il y a trois scènes disponibles, et sur chaque scène, nous avons placé des +pièces dans de nombreuses positions. Lorsqu'un utilisateur commence une +expérience, l'algorithme de selection fonctionne de façon à faire des +expériences sur les mêmes scènes avec les mêmes dispositions de pièces mais des +styles de recommandations différents pour des utilisateurs de même niveau de +sorte à pouvoir comparer l'efficacité des recommandations. + +\paragraph{} +Nous cherchons en fait des expériences qui permettraient de compléter les trio +d'expériences avec des pièces identiques sur des scènes identiques mais avec +des styles de recomendations différents. Si une telle expérience n'existe pas +(les trios sont déjà complets, ou bien il n'existe pas d'expérience pour un +niveau d'utilisateur donné), la scène, ainsi que les pièces à trouver et le +style de recommandations seront choisi aléatoirement : on choisira une scène et +un style de recommandations que l'utilisateur n'a pas encore effectué, et on +choisira 8 pièces aléatoirement parmi les positions possibles que nous avons +fixées au préalable. + +\section{Positions possibles des pièces} +Pour choisir les positions possibles des pièces dans chaque scène, un petit +outil à été développé permettant de se déplacer dans une scène et des créer des +pièces en cliquant. Cliquer sur une paroie de la scène crée une pièce devant +cette paroie, et cliquer sur une pièce la supprime. Un bouton permet d'envoyer +la liste des pièces par mail, lorsque l'on a fini de créer des pièces. -\section{Streaming de modèle 3D} +\newpage +\part{Streaming de modèle 3D} +Le but ultime de ce projet est de biaiser l'utilisateur avec les +recommandations de sorte à être capable de prévoir ses déplacements futurs, et +ainsi précharger les parties du modèle qui vont être vues. Cette section +présente le travail qui a été réalisé dans le domaine du chargement de modèle. +\paragraph{} +Évidemment, cette partie est celle qui comment après la fin de la première +partie : il faut nous seulement connaître l'influence des recommandations sur +l'utilisateur, ensuite être capable de prévoir le comportement de +l'utilisateur, et enfin s'en servir pour précharger les bonnes parties du +modèles. Tout ceci n'étant pas encore possible, le travail qui a été fait est +nettement plus simpliste : il n'y aura aucune prévision du comportement ici. -\section*{Conclusion} +\section*{Introduction} +Notre problématique ici est de transférer des modèles 3D sur le réseau. Les +modèles sont stockés sur le serveur au format \texttt{.obj} et sont constitués +: +\begin{itemize} + \item des materiaux (\texttt{usemtl}) : cela définit le matériau utilisé + pour les faces qui vont suivre + \item de sommets (\emph{vertices}) : des points 3D + \item de coordonnées de textures : des points en 2D qui référence un point + d'une image + \item de normales : des vecteurs en 3D + \item de faces : une liste de 3 ou 4 sommets (représentés par leurs + indices), avec éventuellement leurs coordonnées de textures et/ou + normales +\end{itemize} + +\paragraph{} +La seule contrainte d'ordre des éléments est qu'une face qui contient des +sommets, coordonnées de textures ou normales n'arrive pas avant ses sommets, +coordonnées de textures ou normales dans le fichier : si l'on parcourt les +lignes du fichier, on doit déjà avoir toutes les informations sur la face. + +\paragraph{} +Généralement, le fichier \texttt{.obj} vient souvent avec un fichier +\texttt{.mtl} qui contient la définition des matériaux (leurs noms, leurs +textures, leurs constantes...). + +\newpage +\section{Streaming linéaire} +La première étape de cette fonctionnalité a été de faire un système +client-serveur permettant le streaming de modèle 3D. En effet, les +\emph{loaders} de modèles présents dans la libraire \threejs ne permettent pas +le chargement progressif : ils se content d'envoyer une requête vers le fichier +contenant le modèle et à créer un modèle une fois que le fichier est chargé +complètement. + +\paragraph{} +Pour commencer, nous avons donc utilisé \socketio, une librairie permettant de +gérer les sockets facilement avec JavaScript et Nodejs, pour faire une première +version simpliste du streaming : on travaillait sur un modèle ne contenant que +des sommets et des faces (donc pas de textures ni de normales) et le protocole +fonctionnait ainsi : + +\begin{figure}[H] + \centering + \begin{tikzpicture}[] + \draw (0,0) node[above]{Client}; + \draw (0,0) -- (0,-6); + \draw (5,0) node[above]{Serveur}; + \draw (5,0) -- (5,-6); + + \draw (2.5,-1.3) node[rotate=-12] {Path jusqu'au modèle}; + \draw[->] (0,-1) -- (5, -2); + + \draw (2.5,-2.3) node[rotate=12] {Des éléments}; + \draw[<-] (0,-3) -- (5, -2); + + \draw (2.5,-3.3) node[rotate=-12] {ACK}; + \draw[->] (0,-3) -- (5, -4); + + \draw (2.5,-4.3) node[rotate=12] {D'autres éléments}; + \draw[<-] (0,-5) -- (5, -4); + + \draw (2.5,-6) node{$\vdots$}; + \end{tikzpicture} + \caption{Transmission élémentaire\label{transmission1}} +\end{figure} + +\paragraph{} +Le serveur envoyait alors les éléments dans l'ordre dans lequel ils étaient +présents dans le fichier du modèle 3D. Le gros inconvéniant de cette méthode +est que souvent, les sommets sont présents au début du fichier, et les faces +vers la fin : nous recevons donc des informations de sommets au début qui ne +nous permettent rien d'afficher, puis toutes les faces d'un coup, ce qui fait +que le streaming n'est pas aussi progressif que l'on souhaiterais. + +\paragraph{} +Dans le cas d'un modèle sans coordonnées de textures et sans normales, la seule +condition pour pouvoir afficher une face est d'avoir envoyé les sommets qui la +composent. On peut donc améliorer la fluidité en réarrangeant le fichier +\texttt{.obj} : il suffit de faire apparaître les faces dès que les sommets +sont disponibles. + +\paragraph{} +Dans le cas où l'on souhaite gérer les textures et les normales, utiliser cette +technique est beaucoup plus compliqué puisqu'il faut aussi décider de l'ordre +relatif entre les sommets, coordonnées de textures et normales : en effet, pour +envoyer une face le plus tôt possible, il faut que toutes ces composantes +soient envoyés, et il est donc nécessaire de mélanger les sommets, coordonnées +de texture et normales. + +\newpage +\section{Streaming linéaire amélioré} +La remarque précédente conduit directement à cette méthode. Le principe reste +le même que celui précédent (figure \ref{transmission1}), mais les éléments +seront envoyés différemment : on va en fait parcourir les faces directement. + +\paragraph{} +Le serveur va garder en mémoire ce qui a déjà été envoyé et ce qui ne l'a pas +été, et envoyer les faces dans l'ordre : si certains éléments des faces n'ont +pas encore été envoyé, on les enverra juste avant d'envoyer la face en +question. + +\paragraph{} +On peut de cette façon à la fois gérer les coordonnées de texture et les +normales, tout en envoyant les faces le plus tôt possible (on peut aussi noter +que si certains sommets / coordonnées de textures / normales sont inutilisés +dans les faces, ils ne seront pas envoyés et on s'évite donc de transférer des +données inutiles). + +\paragraph{} +C'est dans cette version que nous avons commencé à nous intéresser à la façon +de gérer les matériaux. Dans \threejs, un objet 3D est lié à un materiau, et +nous sommes donc obligés de créer autant d'objets que de materiaux. Pour cela, +nous avons légèrement modifié notre protocole : + +\begin{figure}[H] + \centering + \begin{tikzpicture}[] + \draw (0,0) node[above]{Client}; + \draw (0,0) -- (0,-8); + \draw (5,0) node[above]{Serveur}; + \draw (5,0) -- (5,-8); + + \draw (2.5,-1.3) node[rotate=-12] {Path jusqu'au modèle}; + \draw[->] (0,-1) -- (5, -2); + + \draw (2.5,-2.3) node[rotate=12] {Liste des matériaux}; + \draw[<-] (0,-3) -- (5, -2); + + \draw[dashed, ->] (-0.1,-3) -- (-0.1,-4); + + \draw (2.5,-3.3) node[rotate=-12] {ACK}; + \draw[->] (0,-3) -- (5, -4); + + \draw (2.5,-4.3) node[rotate=12] {Des éléments}; + \draw[<-] (0,-5) -- (5, -4); + + \draw (2.5,-5.3) node[rotate=-12] {ACK}; + \draw[->] (0,-5) -- (5, -6); + + \draw (2.5,-6.3) node[rotate=12] {D'autres éléments}; + \draw[<-] (0,-7) -- (5, -6); + + \draw (2.5,-8) node{$\vdots$}; + \end{tikzpicture} + \caption{Transmission avec gestion des matériaux} +\end{figure} + +\paragraph{} +Les directives de matériau à utiliser (\texttt{usemtl}) seront ignorées, et les +faces seront envoyées avec l'indice de l'objet auquel elles appartiennent, ce +qui permettra au client de savoir dans quel objet (et donc avec quel matériau) +elles doivent être ajoutées. + +\newpage +\section{Streaming intelligent} +C'est la dernière version du streaming qui a été faite sur ce projet. À chaque +transfert, le client envoie sa position au serveur (ainsi que les plans +définissant son \emph{frustum}\footnote{les bords du champ de vision de la +caméra}) et le serveur va parcourir les faces du modèle en cherchant celles qui +apparaissent dans le \emph{frustum}\footnote{dans cette version, on considère +qu'une face apparaît dans le \emph{frsutum} si un de ces sommets y appartient. +Évidemment, une face très grande pourrait appraître dans le \emph{frustum} +sans qu'aucun de ses sommets n'y soit, mais nous n'avons pas traité ce cas +particulier ici.}. On évite ainsi d'envoyer les faces du modèle qui sont +derrière la caméra et que l'utilisateur ne voit pas. + +\paragraph{} +Bien sûr, si il n'y a plus de faces dans le \emph{frustum} de la caméra, on va +envoyer les faces en suivant la méthode de la section précédente. + +\begin{figure}[H] + \centering + \begin{tikzpicture}[] + \draw (0,0) node[above]{Client}; + \draw (0,0) -- (0,-8); + \draw (5,0) node[above]{Serveur}; + \draw (5,0) -- (5,-8); + + \draw (2.5,-1.3) node[rotate=-12] {Path jusqu'au modèle}; + \draw[->] (0,-1) -- (5, -2); + + \draw (2.5,-2.3) node[rotate=12] {Liste des matériaux}; + \draw[<-] (0,-3) -- (5, -2); + + \draw[dashed, ->] (-0.1,-3) -- (-0.1,-4); + + \draw (2.5,-3.3) node[rotate=-12] {Caméra / \emph{frustum}}; + \draw[->] (0,-3) -- (5, -4); + + \draw (2.5,-4.3) node[rotate=12] {Des éléments}; + \draw[<-] (0,-5) -- (5, -4); + + \draw (2.5,-5.3) node[rotate=-12] {Caméra / \emph{frustum}}; + \draw[->] (0,-5) -- (5, -6); + + \draw (2.5,-6.3) node[rotate=12] {D'autres éléments}; + \draw[<-] (0,-7) -- (5, -6); + + \draw (2.5,-8) node{$\vdots$}; + \end{tikzpicture} + \caption{Version finale} +\end{figure} + +\newpage +\part*{Conclusion} \label{end}\end{document}