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>
93 lines
4.4 KiB
Markdown
93 lines
4.4 KiB
Markdown
# 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=<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`](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 |
|