# viewer — le viewer et l'éditeur de scène 3D Application web **autonome** (décision D3 de [`../docs/02-decisions.md`](../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 ```bash 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=` (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`](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 |