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);
}
}
}