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

77 lines
3.1 KiB
C#

using MyInfoMate.Vr.Manifest;
using MyInfoMate.Vr.Scene;
using UnityEngine;
namespace MyInfoMate.Vr.Boot
{
/// <summary>
/// Point d'entrée de l'étape S2 : la scène n'est plus codée en dur, elle est
/// <b>construite à partir d'un JSON</b>.
///
/// Remplace <see cref="S1Bootstrap"/>, à garder côte à côte le temps de valider
/// S1 : l'un prouve la chaîne technique, l'autre prouve le contrat de données.
///
/// Le manifeste est ici un fichier de StreamingAssets, poussé sur le casque à la
/// main (<c>adb push</c> ou MQDH). En S6 il viendra du réseau et du cache disque
/// — et <see cref="SceneBuilder"/> ne changera pas d'une ligne, c'est le but.
/// </summary>
public class S2Bootstrap : MonoBehaviour
{
[Header("Manifeste dans Assets/StreamingAssets")]
[SerializeField] string manifestFile = "scene.json";
[Header("Langue de démarrage — vide = langue par défaut du manifeste")]
[SerializeField] string language = "";
SceneBuilder _builder;
async void Start()
{
var head = Camera.main != null ? Camera.main.transform : null;
if (head == null)
Debug.LogError("[S2] Aucune caméra active : le rig VR n'est pas dans la scène. " +
"Meta → Tools → Building Blocks → Camera Rig.");
var rig = head != null ? head.root : null;
var url = ManifestUrl();
var result = await ManifestReader.LoadAsync(url);
if (!result.Ok)
{
// Le refus est le livrable : on affiche la phrase, on ne charge rien,
// et on ne laisse pas une scène vide faire croire à une panne.
SceneMessage.Show(result.Error, head);
return;
}
_builder = gameObject.AddComponent<SceneBuilder>();
var chosen = string.IsNullOrEmpty(language) ? result.Manifest.DefaultLanguage : language;
if (!await _builder.BuildAsync(result.Manifest, head, rig, chosen))
SceneMessage.Show("Le décor de cette scène n'a pas pu être chargé.", head);
}
/// <summary>
/// Un manifeste poussé sur le casque l'emporte sur celui de l'APK :
/// <c>adb push scene.json /sdcard/Android/data/&lt;package&gt;/files/</c>.
///
/// C'est ce qui rend S2 vérifiable. Lu depuis StreamingAssets, le manifeste
/// est <b>dans</b> l'APK : changer une valeur demanderait un rebuild, et le
/// critère « on modifie le JSON, on relance, sans recompiler » n'aurait plus
/// d'objet. En S6 la même priorité vaudra pour le cache disque.
/// </summary>
string ManifestUrl()
{
var pushed = System.IO.Path.Combine(Application.persistentDataPath, manifestFile);
if (!System.IO.File.Exists(pushed))
return GltfLoader.StreamingAssetsUrl(manifestFile);
Debug.Log($"[S2] Manifeste poussé : {pushed}");
return new System.Uri(pushed).AbsoluteUri;
}
}
}