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>
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 :
- placer trois objets dans le viewer, à des endroits asymétriques ;
- exporter le
scene.json; - le pousser sur le casque (
adb push … /sdcard/…, ou le remettre dansStreamingAssets) ; - 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.
- Fichiers statiques.
base: './', décodeurs Draco/KTX2 copiés en local, aucun CDN. Le bundle dedist/est embarquable dans les assets d'une app Flutter et servable depuisfile://. - 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. - Le manifeste est une entrée, pas un fetch. Trois chemins :
postMessage(le casmanager-appen S5, et le cas offline),?manifest=<url>(le cas S4), glisser-déposer (le cas d'aujourd'hui). Le glisser-déposer réécritassets[].urlenblob:— 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 |