using System; using System.Threading.Tasks; using MyInfoMate.Vr.Net; using UnityEngine; using UnityEngine.Video; namespace MyInfoMate.Vr.Menu { /// /// Le fond immersif d'une visite — le reste de l'item E5, spécifié au §4 de /// DOCS/v2/immersif-frontiere-plan.md. /// /// Sans lui, le menu flotte dans le noir. C'est le défaut d'Unity : une scène /// vide est un vide, et un visiteur qui met le casque se retrouve devant six panneaux /// suspendus dans le néant. Avec le panorama du lieu derrière, il est dans le /// musée avant d'avoir choisi quoi que ce soit. /// /// Trois différences avec , et elles tiennent toutes à la même /// chose — un fond n'est pas un contenu : /// /// on ne le choisit pas et on n'en sort pas : aucun panneau de retour ; /// il reste en place tant que la visite dure, y compris entre deux sections ; /// il s'efface devant une 360 ouverte, puis revient — SkyboxView restaure /// le ciel précédent en se détruisant, et le ciel précédent, c'est celui-ci. /// /// /// Son absence est un cas nominal : un lieu sans fond garde le noir, sans un mot. /// public class ImmersiveBackdrop : MonoBehaviour { const int VideoWidth = 4096; const int VideoHeight = 2048; Material _sky; Texture2D _image; VideoPlayer _player; RenderTexture _target; /// /// Pose le fond de cette visite, s'il y en a un. Rend le composant créé, ou null — /// pas de fond n'est pas une erreur. /// public static async Task ApplyAsync( ConfigurationExport export, ApiClient client) { var background = export?.ImmersiveBackground; if (background == null || string.IsNullOrEmpty(background.ResourceUrl)) return null; // Une scène 3D en fond est du ressort du viewer de scène, pas du ciel : la // rendre ici donnerait un décor plaqué sur une sphère. Le §4 la prévoit, E7 // sait la charger — le jour où on les branche, c'est ici que ça se décide. if (background.Kind == ConfigurationExport.ImmersiveBackgroundKind.Scene3D) { Debug.Log("[Backdrop] Fond de type Scène 3D : pas encore rendu en fond, " + "le menu reste sur le ciel par défaut."); return null; } var root = new GameObject("ImmersiveBackdrop"); var backdrop = root.AddComponent(); await backdrop.LoadAsync(background, client); return backdrop; } async Task LoadAsync(ConfigurationExport.Backdrop background, ApiClient client) { _sky = PanoramicSky.CreateMaterial(); if (_sky == null) return; if (background.Kind == ConfigurationExport.ImmersiveBackgroundKind.Video360) PlayVideo(background, client); else await ShowImage(background, client); } async Task ShowImage(ConfigurationExport.Backdrop background, ApiClient client) { var texture = await PanoramicSky.LoadEquirectangularAsync(client, background.ResourceUrl); if (texture == null || this == null) return; _image = texture; _sky.SetTexture("_MainTex", texture); RenderSettings.skybox = _sky; } /// /// ⚠️ Une vidéo en fond tourne en permanence. Sur une borne qui fonctionne /// huit heures, c'est un décodeur matériel occupé toute la journée et une batterie /// qui descend plus vite — à mesurer avant de la vendre comme le cas normal. Le son /// est coupé : un fond qui parle par-dessus le commentaire d'une section serait /// intenable. /// void PlayVideo(ConfigurationExport.Backdrop background, ApiClient client) { _target = new RenderTexture(VideoWidth, VideoHeight, 0); _player = gameObject.AddComponent(); _player.source = VideoSource.Url; _player.renderMode = VideoRenderMode.RenderTexture; _player.targetTexture = _target; _player.isLooping = true; _player.audioOutputMode = VideoAudioOutputMode.None; _player.errorReceived += (_, message) => Debug.LogError($"[Backdrop] Lecture vidéo impossible : {message}"); _sky.SetTexture("_MainTex", _target); RenderSettings.skybox = _sky; StartPlayback(background.ResourceUrl, client); } async void StartPlayback(string url, ApiClient client) { var path = await ContentCache.MediaPathAsync(client, url); if (path == null || this == null || _player == null) return; _player.url = new Uri(path).AbsoluteUri; _player.Play(); } void OnDestroy() { // Le ciel est un réglage global. On ne le remet à null que s'il est encore le // nôtre : une 360 ouverte par-dessus a déjà pris la place, et lui arracher son // ciel en repassant par ici la laisserait dans le noir. if (RenderSettings.skybox == _sky) RenderSettings.skybox = null; if (_player != null) _player.Stop(); if (_target != null) _target.Release(); if (_image != null) Destroy(_image); } } }