using UnityEngine; namespace MyInfoMate.Vr.Scene { /// /// Zone de navigation circulaire, plafonnée à 3 m de rayon (§3.2 du cahier des /// charges). Le plafond est appliqué ici et côté serveur : un client qui /// fait confiance à la donnée est un client qu'on casse avec une donnée fausse. /// /// Deux rôles : matérialiser la limite comme le garde-fou natif du Quest, et /// ramener le visiteur à l'intérieur s'il en sort. On ne téléporte pas — on /// repousse : une téléportation surprise dans un casque donne la nausée. /// public class NavigationBounds : MonoBehaviour { public const float MaxRadiusMeters = 3f; [SerializeField] float radiusMeters = 3f; [SerializeField] Transform visitor; [SerializeField] Transform head; [SerializeField] float fadeStartMeters = 0.4f; LineRenderer _ring; public void Configure(Transform visitorRig, Transform visitorHead, float radius, bool showBoundary) { visitor = visitorRig; head = visitorHead; radiusMeters = Mathf.Min(radius, MaxRadiusMeters); if (showBoundary) DrawRing(); } void DrawRing() { const int segments = 96; _ring = gameObject.AddComponent(); _ring.useWorldSpace = false; _ring.loop = true; _ring.positionCount = segments; _ring.widthMultiplier = 0.015f; _ring.material = new Material(Shader.Find("Universal Render Pipeline/Unlit")); _ring.material.color = new Color(0.4f, 0.8f, 1f, 0.6f); for (var i = 0; i < segments; i++) { var angle = i * Mathf.PI * 2f / segments; _ring.SetPosition(i, new Vector3( Mathf.Cos(angle) * radiusMeters, 0.01f, Mathf.Sin(angle) * radiusMeters)); } } /// /// On mesure la tête et on déplace le rig. En VR le rig ne bouge /// pas quand le visiteur marche : le tracking applique le déplacement à la /// caméra, à l'intérieur du rig. Mesurer le rig, c'est ne jamais rien mesurer — /// la contrainte ne se déclenche alors pour aucune distance parcourue. /// /// On retranche l'excédent au rig plutôt que de poser la tête sur le cercle : /// la tête suit le corps, on ne peut que déplacer le repère sous elle. /// void LateUpdate() { if (visitor == null || head == null) return; var local = transform.InverseTransformPoint(head.position); var flat = new Vector2(local.x, local.z); if (flat.magnitude <= radiusMeters) return; var excess = flat - flat.normalized * radiusMeters; visitor.position -= transform.TransformVector(new Vector3(excess.x, 0f, excess.y)); } /// Distance à la limite, pour l'assombrissement progressif du bord. public float ProximityToEdge() { if (head == null) return 0f; var local = transform.InverseTransformPoint(head.position); var distance = new Vector2(local.x, local.z).magnitude; return Mathf.InverseLerp(radiusMeters - fadeStartMeters, radiusMeters, distance); } } }