Thomas Fransolet 0310d28b5e Canal VR Meta Quest : projet Unity, viewer web et documentation
Premier commit du cinquième front. Trois parties :

- `unity/MyInfoMateVR/` : le projet Unity (6000.0.83f1, URP, Meta XR SDK 205),
  un APK unique pour tous les clients. Menu flottant à sélection au regard,
  appairage, chargement de scène GLB, POI, cache de contenu, télémétrie.
- `unity-overlay/` : les mêmes scripts à recopier sur un projet Unity neuf, avec
  les pièges rencontrés consignés dans son README.
- `viewer/` : viewer et éditeur de scène web autonome (Vite, TypeScript,
  three.js), partagé avec les autres fronts.
- `docs/` : état des lieux, setup Unity, décisions d'architecture et plan
  d'exécution en 9 étapes.

La scène est décrite par un `scene.json` poussé par `adb push` : l'app le
préfère à celui embarqué dans l'APK.

Les binaires (GLB, textures de l'échantillon Sponza, DLL Meta XR) passent par
Git LFS dès ce premier commit — les y faire entrer après coup demanderait de
réécrire l'historique. Les artefacts régénérés par l'éditeur et par CMake
(`Library/`, `.utmp/`, Burst debug) sont ignorés.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 15:26:07 +02:00
..

viewer — le viewer et l'éditeur de scène 3D

Application web autonome (décision D3 de ../docs/02-decisions.md). Elle affiche une scène 3D décrite par un manifeste, et permet de composer ce manifeste à la souris.

C'est l'étape S3 du plan. Elle ne contient aucun Flutter et aucun appel au backend — c'est ce qui la rend faisable pendant que le casque est bloqué.

Démarrer

npm install      # copie aussi les décodeurs Draco/KTX2 dans public/libs/
npm run dev      # http://localhost:5180
npm run build    # typecheck + bundle statique dans dist/

Puis glisser-déposer dans la fenêtre le scene.json et les .glb qu'il référence, ensemble. Le fichier d'exemple est celui de S2 : ../unity/MyInfoMateVR/Assets/StreamingAssets/scene.json, avec world.glb, calibration.glb et persona.glb du même dossier.

Les gestes

Geste Effet
clic sélectionner
glisser déplacer au sol (XZ)
Alt + glisser régler la hauteur (Y) — indispensable pour un point d'intérêt
Maj + glisser pivoter (lacet)
Échap désélectionner
Ctrl+Z / Ctrl+Maj+Z annuler / refaire

Les valeurs numériques du panneau sont éditables et passent par le même undo : poser à la souris est confortable mais imprécis, « ce panneau à exactement 1,40 m » se tape.

Le test qui compte

C'est le critère de validation de S3, et c'est la seule preuve valable de la convention d'axes :

  1. placer trois objets dans le viewer, à des endroits asymétriques ;
  2. exporter le scene.json ;
  3. le pousser sur le casque (adb push … /sdcard/…, ou le remettre dans StreamingAssets) ;
  4. les trois objets sont exactement là où ils ont été mis.

Une scène symétrique ne prouve rien : un miroir d'axes y est invisible. C'est précisément le bug que GltfSpace et calibration.glb existent pour attraper.

Ce que ce viewer n'est pas

Un aperçu indicatif, et le mot est dans l'interface — pas seulement ici. Sont alignés sur Unity : tone mapping ACES, exposition du manifeste, environnement neutre, PBR core glTF. Ne sont pas garantis : ombres, post-traitement, matériaux exotiques. Sans cet avertissement, un client valide une couleur qu'il ne retrouvera pas au casque.

Ce n'est pas non plus un éditeur 3D : pas de gizmos, pas de hiérarchie, pas de matériaux. Le client compose, il ne modélise pas (§8 de la conception).

Les trois règles de D3, et pourquoi elles sont déjà là

Elles ne coûtent rien maintenant et coûtent une réécriture au moment du portage.

  1. Fichiers statiques. base: './', décodeurs Draco/KTX2 copiés en local, aucun CDN. Le bundle de dist/ est embarquable dans les assets d'une app Flutter et servable depuis file://.
  2. Aucune URL d'API en dur. Toutes les URL viennent de assets[].url. Un asset se désigne par son identifiant dans l'interface, jamais par une URL.
  3. Le manifeste est une entrée, pas un fetch. Trois chemins : postMessage (le cas manager-app en S5, et le cas offline), ?manifest=<url> (le cas S4), glisser-déposer (le cas d'aujourd'hui). Le glisser-déposer réécrit assets[].url en blob: — exactement ce que fera le pipeline offline avec des chemins locaux, donc ce scénario est testé tous les jours.

Où il ira ensuite

Front Intégration Étape
manager-app (Flutter Web) iframe HtmlElementView + postMessage S5
visitapp-web (Next.js) composant, lecture seule (mode: 'view') hors V1
mymuseum-visitapp (Flutter) WebView, bundle embarqué, hors ligne hors V1

Le protocole postMessage est déjà écrit (src/bridge.ts) alors que son premier consommateur n'existe pas : c'est la règle 3 ci-dessus.

Les fichiers

Fichier Rôle
src/manifest.ts Les types du manifeste et sa validation. Miroir de SceneManifest.cs — modifier l'un sans l'autre casse le test croisé
src/Viewer.ts three.js : scène, caméra, chargement glTF, tone mapping aligné sur Unity
src/Editor.ts Sélection, placement, rotation, undo, état « non enregistré »
src/Bounds.ts Le cercle de navigation et la grille au mètre, à l'échelle
src/Panel.ts Listes, valeurs numériques, contenu multilingue des points d'intérêt, budget
src/bridge.ts Le protocole postMessage
src/main.ts Le câblage, l'export, le glisser-déposer