Commit
|
@ -0,0 +1 @@
|
||||||
|
\part*{Conclusion}
|
After Width: | Height: | Size: 584 KiB |
After Width: | Height: | Size: 601 KiB |
After Width: | Height: | Size: 588 KiB |
After Width: | Height: | Size: 695 KiB |
After Width: | Height: | Size: 675 KiB |
|
@ -0,0 +1,17 @@
|
||||||
|
i_min = 167;
|
||||||
|
j_min = 391;
|
||||||
|
i_max = 933;
|
||||||
|
j_max = 1524;
|
||||||
|
|
||||||
|
repo = 'screenshots';
|
||||||
|
|
||||||
|
shots = dir(repo);
|
||||||
|
|
||||||
|
for i = 1:length(shots)
|
||||||
|
i
|
||||||
|
if (strcmp(shots(i).name, '.') || strcmp(shots(i).name, '..'))
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
im = imread(strcat(repo, '/', shots(i).name));
|
||||||
|
imwrite(im(i_min:i_max,j_min:j_max,:), strcat('/home/thomas/stage/rapport/rapport/img/new/', shots(i).name)) ;
|
||||||
|
end
|
After Width: | Height: | Size: 670 KiB |
After Width: | Height: | Size: 692 KiB |
After Width: | Height: | Size: 676 KiB |
After Width: | Height: | Size: 793 KiB |
|
@ -0,0 +1,188 @@
|
||||||
|
\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.
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
La translation de la caméra est contrôlée par le clavier : les touches Z, Q, S,
|
||||||
|
et D servent respectivement à avancer, aller à gauche, reculer et aller à
|
||||||
|
droite (de même que les touches fléchées).
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
On peut pivoter la caméra de plusieurs manières :
|
||||||
|
\begin{itemize}
|
||||||
|
\item via le pavé numérique (2, 4, 6, et 8 pour tourner respectivement vers
|
||||||
|
le bas, vers la gauche, vers la droite et vers le haut)
|
||||||
|
\item via la souris, comme \emph{drag-n-drop}, en cliquant un point de la
|
||||||
|
scène et en le déplaçant
|
||||||
|
\item via la souris, en mode \emph{pointer-lock}, comme dans un jeu video
|
||||||
|
de tir
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
|
||||||
|
\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.
|
||||||
|
|
||||||
|
|
||||||
|
\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.
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[scale=0.275]{img/new/01.png}
|
||||||
|
\caption{Une recommandation \emph{viewport}}
|
||||||
|
\end{figure}
|
||||||
|
Cette façon d'afficher une recommandation a l'avantage d'être simple, de ne pas
|
||||||
|
beaucoup masquer le reste des modèles et suggère assez bien l'idée d'un
|
||||||
|
\emph{point de vue recommandé}, mais elle a l'inconvéniant d'être ambigüe à
|
||||||
|
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).
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Les flèches}
|
||||||
|
\subsubsection{Principe}
|
||||||
|
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
|
||||||
|
un point de vue, elles suggèrent le mouvemement qui va mener à ce point de vue.
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[scale=0.275]{img/new/02.png}
|
||||||
|
\caption{Des recommandations flèches}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\subsubsection{Courbure de la flèche}
|
||||||
|
Pour le dessin des flèches, plusieurs choses sont à prendre en compte :
|
||||||
|
\begin{itemize}
|
||||||
|
\item il faut éviter que la flèche soit trop \emph{présente} à l'écran et
|
||||||
|
qu'elle obstrue trop le reste de la scène
|
||||||
|
\item il faut que la flèche soit dans un plan qui ne soit pas orthogonal au
|
||||||
|
plan image de la caméra. En effet, si la flèche est dans un plan
|
||||||
|
orthogonal au plan image de la caméra, elle se projettera comme un
|
||||||
|
segment et on ne pourra pas voir la courbure de la flèche.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
Pour trouver la courbure de la flèche, nous posons $C$ le centre de la caméra,
|
||||||
|
$R$ le centre de la recommandation, et $R'$ le vecteur qui donne la direction
|
||||||
|
de la recommandation. Nous cherchons ensuite un polynôme $f$ tel que :
|
||||||
|
$$\left\{\begin{array}{lcl}
|
||||||
|
f(0) & = & C \\
|
||||||
|
f(1) & = & R \\
|
||||||
|
f'(1) & = & \lambda R' \text{ avec } \lambda \in \mathbb{R}^{+}
|
||||||
|
\end{array}\right.$$
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
Le problème de ce polynôme est qu'il ne vérifie aucune des contraintes énoncées
|
||||||
|
précédemment : puisque $f(0) = C$, le bout de la flèche est dans la caméra, et
|
||||||
|
va donc masquer toute la scène, et la courbe va en plus se projeter sur une
|
||||||
|
ligne sur l'écran.
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
Pour solutionner le premier problème, nous nous contenterons d'afficher
|
||||||
|
seulement la flèche pour des instants $t \in [0.5, 1]$.
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
Pour solutionner le deuxième problème, nous allons translater le centre de la
|
||||||
|
caméra vers le bas et le côté de la direction de la recommandation, pour
|
||||||
|
accentuer la courbure de la flèche, et nous résolvons plutôt le système suivant
|
||||||
|
:
|
||||||
|
$$\left\{\begin{array}{lcl}
|
||||||
|
f(0) & = & C - e_z + \lambda R' \\
|
||||||
|
f(1) & = & R \\
|
||||||
|
f'(1) & = & \lambda R' \text{ avec } \lambda \in \mathbb{R}^{+}
|
||||||
|
\end{array}\right.$$
|
||||||
|
|
||||||
|
\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é.
|
||||||
|
Nous avons simplement adapté cette techniques à nos recommandations : lorsque
|
||||||
|
le curseur survole une recommandation, une prévisualisation est affichée dans
|
||||||
|
une petite boite au voisinage du curseur.
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[scale=0.275]{img/new/03.png}
|
||||||
|
\caption{Une prévisualisation}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\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 :
|
||||||
|
\begin{itemize}
|
||||||
|
\item la position initiale est la position de la caméra
|
||||||
|
\item la position finale est la position de la recommandation
|
||||||
|
\item la dérivée de la trajectoire à l'instant final est la direction de la
|
||||||
|
recommandation
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
Ce mouvement fluide est là pour ne pas perturber l'utilisateur qui pourrait
|
||||||
|
\emph{se perde} si jamais il était téléporté
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
De plus, les recommandations se comportent comme des liens hypertextes : elles
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
\section{Autres éléments de navigation}
|
||||||
|
\paragraph{}
|
||||||
|
Pour faciliter la navigation, quelques autres éléments de navigation sont
|
||||||
|
présents.
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\begin{tikzpicture}[]
|
||||||
|
\node (myfirstpic) at (0,0) {\includegraphics[scale=0.35]{img/new/buttons.png}};
|
||||||
|
\tikzvline{-7}{1}
|
||||||
|
\tikzvline{-5.8}{2}
|
||||||
|
\tikzvline{-5.2}{3}
|
||||||
|
\tikzvline{-4}{4}
|
||||||
|
\tikzvline{-1.5}{5}
|
||||||
|
|
||||||
|
\draw (8,5) -- (7,5);
|
||||||
|
\draw (8,5) node[right]{6};
|
||||||
|
|
||||||
|
\draw (-8,5) -- (-7,5);
|
||||||
|
\draw (-8,5) node[left]{7};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\caption{Les différents éléments de l'interface}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
\begin{enumerate}
|
||||||
|
|
||||||
|
\item \emph{Reset camera} : pour chaque scène, une position initiale est
|
||||||
|
définie. Cliquer sur ce bouton ramène la caméra à sa position initiale.
|
||||||
|
|
||||||
|
\item \emph{Previous} : à chaque clic sur une recommandation, les positions
|
||||||
|
intiales et finales sont sauvegardées. Cliquer sur ce bouton ramène à
|
||||||
|
la position précédente.
|
||||||
|
|
||||||
|
\item \emph{Next} : cliquer sur ce bouton ramène à la position suivante.
|
||||||
|
|
||||||
|
\item \emph{Pointer lock} : permet de passer du mode \emph{pointer-lock} au
|
||||||
|
mode \emph{drag-n-drop} et vice-versa.
|
||||||
|
|
||||||
|
\item \emph{Music} : un lecteur qui contrôle une petite musique qui permet
|
||||||
|
de se mettre dans l'ambiance de la scène.
|
||||||
|
|
||||||
|
\item \emph{Coin gauge} : une jauge qui représente l'avancement de la
|
||||||
|
récupération des pièces.
|
||||||
|
|
||||||
|
\item \emph{FPS counter} : indique la période de rafraîchissement du rendu.
|
||||||
|
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
\part*{Introduction}
|
|
@ -0,0 +1,220 @@
|
||||||
|
\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*{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...).
|
||||||
|
|
||||||
|
|
||||||
|
\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.
|
||||||
|
|
||||||
|
|
||||||
|
\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.
|
||||||
|
|
||||||
|
|
||||||
|
\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}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
\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.
|
||||||
|
|
||||||
|
\section{Côté client}
|
||||||
|
\paragraph{}
|
||||||
|
Pour le côté client, il y avait plusieurs possibilités :
|
||||||
|
\begin{itemize}
|
||||||
|
\item WebGL, la spécification des fonctions permettant la 3D dans le
|
||||||
|
navigateur
|
||||||
|
\item Une librairie facilitant l'utilisation de WebGL
|
||||||
|
\item Du code C++ compilé en JavaScript grâce à Emscripten
|
||||||
|
\item N'importe quel moteur graphique qui puisse exporter vers JavaScript
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\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 \threejs permettant une
|
||||||
|
utilisation facile de WebGL.
|
||||||
|
|
||||||
|
\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
|
||||||
|
problématiques de dynamicité sont arrivées, il a fallu choisir une technologie
|
||||||
|
pour le côté serveur, et là, tous les langages étaient possibles.
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
Plusieurs langages et framework ont été téstés. Quand les problématiques
|
||||||
|
étaient encore simples (passage d'un paramètre dans une requête), on a commencé
|
||||||
|
par utiliser le php, puis on s'est tourné vers des scripts CGI en python. Quand
|
||||||
|
de plus nombreuses pages ont été nécessaires, on a commencé à chercher un vrai
|
||||||
|
framework, et on s'est penché sur Django (framework web pour Python) qui est
|
||||||
|
très pratique mais assez coûteux en mémoire vive (le serveur était alors
|
||||||
|
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 \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).
|
||||||
|
|
||||||
|
\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
|
||||||
|
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.
|
||||||
|
|
||||||
|
\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
|
||||||
|
automatiquement. Pour le développement, ils seront simplement concaténés grâce
|
||||||
|
à un script développé spécialement pour cela, qui mime les paramètres de
|
||||||
|
Closure Compiler qui sera utilisé pour la fusion au moment du le déploiement
|
||||||
|
(ce dernier permet non seulement la fusion des fichiers mais aussi la
|
||||||
|
minification\footnote{la minification sert notamment à réduire la taille du
|
||||||
|
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 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).
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
En ce qui concerne le versionnage des fichiers, nous avons utilisé Git avec
|
||||||
|
deux \emph{repositories} :
|
||||||
|
\begin{itemize}
|
||||||
|
\item le premier, hébergé sur
|
||||||
|
\href{https://github.com/tforgione/3dinterface}{Github}, sert au
|
||||||
|
développement, et contient les fichiers fractionnés ainsi que les
|
||||||
|
outils permettant la génération des fichiers fusionnés.
|
||||||
|
\item le deuxième, hebergé chez OpenShift, qui contient la version finale
|
||||||
|
du programme, permet de déployer le code du serveur quand les
|
||||||
|
\emph{commits} sont \emph{pushés}.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
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{Documentation}
|
||||||
|
Au delà des rapports, deux documentations sont présentes.
|
||||||
|
|
||||||
|
\subsection{\href{https://github.com/tforgione/3dinterface/wiki}{\emph{Github Wiki}}}
|
||||||
|
Github permet la création de Wiki pour chaque \emph{repository} et nous nous en
|
||||||
|
sommes servi pour de la documentation de haut niveau : il ne présente que des
|
||||||
|
aspects théoriques de ce qui a été réalisé pendant ce projet.
|
||||||
|
|
||||||
|
\subsection{\href{http://l3d.no-ip.org/}{L3D}}
|
||||||
|
Pour de la documentation de plus bas niveau (comment chaque classe ou méthode
|
||||||
|
fonctionne) nous avons utilisé \jsdoc (équivalent de javadoc mais pour
|
||||||
|
JavaScript) et nous générons automatiquement des pages html pour avoir une
|
||||||
|
documentation lisible et à jour sans avoir à parcourir le code.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
\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é
|
||||||
|
à des utilisateurs de les trouver.
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[scale=0.275]{img/new/04.png}
|
||||||
|
\caption{Une pièce rouge}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
Pour éviter la dépendance entre les recomendations et les pièces rouges (si les
|
||||||
|
recommandations visent les pièces rouges, il est évident qu'il sera très facile
|
||||||
|
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{Génération des expériences}
|
||||||
|
\subsection{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.
|
||||||
|
|
||||||
|
\subsection{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{Collecte des informations}
|
||||||
|
Ces expérienes n'auront d'interêt que si nous sommes capables de les analyser
|
||||||
|
par la suite et de comprendre comment les utilisateurs interagissent avec les
|
||||||
|
recommandations. Nous avons donc mis en place un système de collecte des
|
||||||
|
actions de l'utilisateur basé sur les XmlHttpRequests de JavaScript.
|
||||||
|
|
||||||
|
Côté serveur, il y a quelques urls qui permettent d'enregistrer des
|
||||||
|
informations dans des tables prévues à cet effet. Chaque évènement contient la
|
||||||
|
date à laquelle il a été envoyé par le client ainsi que l'id du client et de
|
||||||
|
l'expérience qu'il est en train de faire (ces deux cerniers sont stockés dans
|
||||||
|
la session sur le serveur). Les évènements enregistrés sont les suivants :
|
||||||
|
|
||||||
|
\paragraph{ArrowClicked} : crée quand l'utilisateur clique une recommandation,
|
||||||
|
accompagné de l'id de la recommandation cliquée.
|
||||||
|
|
||||||
|
\paragraph{CoinClicked} : crée quand l'utilisateur récupère une pièce rouge.
|
||||||
|
|
||||||
|
\paragraph{KeyboardEvent} : crée quand l'utilisateur appuie ou relâche une
|
||||||
|
touche du clavier, accompagné de la position courante de la caméra et d'un
|
||||||
|
booléen indiquant si elle a été appuyée ou relâchée. Dans le cas du
|
||||||
|
\emph{drag-n-drop} ou du \emph{pointer lock}, on créera de temps en temps
|
||||||
|
quelques évènements de type \texttt{KeyboardEvent} non associée à une touche de
|
||||||
|
sorte à connaître l'angle de la caméra.
|
||||||
|
|
||||||
|
\paragraph{ResetClicked} : crée quand l'utilisateur réinitialise la position de
|
||||||
|
la caméra.
|
||||||
|
|
||||||
|
\paragraph{PreviousNextClicked} : crée quand l'utilisateur clique sur les
|
||||||
|
boutons précédente ou suivante, on stockera la position finale en base de
|
||||||
|
données.
|
||||||
|
|
||||||
|
\paragraph{Hovered} : crée quand l'utilisateur survole ou sort d'une
|
||||||
|
recommandation avec le curseur.
|
||||||
|
|
||||||
|
\paragraph{PointerLocked} : dans le cas où l'utilisateur utilise l'option
|
||||||
|
\emph{pointer lock}, il sera crée au moment où le pointeur sera capturé et où
|
||||||
|
il sera libéré.
|
||||||
|
|
||||||
|
\paragraph{SwitchedLockOption} : crée quand l'utilisateur change d'option entre
|
||||||
|
\emph{pointer locked} et \emph{drag-n-drop}.
|
||||||
|
|
||||||
|
\paragraph{FpsCounter} : chaque seconde, cet évènement est crée pour connaître
|
||||||
|
le \emph{framerate} du client, de sorte à savoir si les performances de sa
|
||||||
|
machine ont pu lui poser problème dans ces expériences.
|
||||||
|
|
||||||
|
\section{Replay}
|
||||||
|
Afin de nous assurer que toutes les informations nécessaires étaient bel et
|
||||||
|
bien récupérées en base de données, nous avons mis en place un programme
|
||||||
|
permettant de rejouer les expériences stockées.
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
Pour le replay, nous avons simplement considéré les évènements
|
||||||
|
\texttt{ArrowClicked}, \texttt{CoinClicked}, \texttt{KeyboardEvent},
|
||||||
|
\texttt{ResetClicked} et \texttt{PreviousNextClicked}, les autres évènements
|
||||||
|
n'ayant pas d'influence sur la position de la caméra.
|
||||||
|
|
||||||
|
|