\mypart{Streaming de modèle 3D\label{streaming}} 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 de 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 commence après la fin de la première partie : il faut non seulement connaître l'influence des recommandations sur l'utilisateur, mais aussi être capable de prévoir le comportement de l'utilisateur, et enfin de s'en servir pour précharger les bonnes parties du modèle. Tout ceci n'étant pas encore possible, le travail qui a été fait est une version simplifiée : 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 matériaux (\texttt{usemtl}) : cela définit le matériau utilisé pour les faces qui vont suivre. Un matériau est notamment défini par ses constantes de réflexions optiques, ses textures, etc... \item de sommets (\emph{vertices}) : des points 3D \item de coordonnées de textures : des points en 2D qui référencent 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{Architecture} Puisque cette partie traite à la fois du client et du serveur, les fichiers peuvent sembler éparpillés. Les fichiers concernés sont les fichiers du répertoire \texttt{geo} pour le serveur et le fichier \texttt{ProgressiveLoader.js} qui est le client. \subsection{Serveur} Nous avons rappelé que les modèles au format \texttt{.obj} contenaient plusieurs \emph{sous-modèles} avec des matériaux différents, le serveur connait les classes suivantes : \begin{itemize} \item la classe \texttt{Mesh}, qui représente un \emph{sous-modèle}, avec ses faces, ses coordonnées de textures, ses normales, son matériau \item la classe \texttt{MeshContainer}, qui représente un modèle au format \texttt{.obj}, en tant que liste de \texttt{Mesh} \item la classe \texttt{MeshStreamer}, qui permettra d'envoyer un modèle au client \end{itemize} \paragraph{} Pour avoir une bonne performance, il faut éviter de re-parser les fichiers \texttt{.obj} et de recréer les \texttt{MeshContainer} à chaque requête. \paragraph{} Dans le fichier \texttt{MeshContainer.js}, une liste de modèle à charger (ainsi que le chemin permettant d'accéder aux fichiers) permet au serveur de créer les \texttt{MeshContainer} au démarrage, et ainsi, le \texttt{MeshStreamer} n'a qu'à prendre une référence vers ce \texttt{MeshContainer} (qui existe déjà, ce qui réduit la latence). \subsection{Client} Le client de la partie \emph{streaming} de ce projet est contenue dans la classe \texttt{ProgressiveLoader}. Celle-ci crée des modèles 3D vides, puis établit la connexion avec le serveur et remplit les modèles au fur et à mesure que les informations arrivent du serveur. Dans les sections suivantes, nous allons montrer les différentes stratégies de \emph{streaming} que nous avons mises en place. \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 simplifiée 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énient 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 souhaiterait. \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és, 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}. On évite ainsi d'envoyer les faces du modèle qui sont derrière la caméra et que l'utilisateur ne voit pas. \begin{figure}[H] \centering \include{../common/build/frustum} \caption{Le frustum de la camera et différents objets\label{frustum-draw}} \end{figure} Dans cette version, on considère qu'une face apparaît dans le \emph{frustum} si un de ses 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 (dans la figure \ref{frustum-draw}, seuls les triangles verts et bleus seront envoyés, bien que le triangle rouge devrait être envoyé lui aussi). \paragraph{} Bien sûr, s'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}