// WorldPanelManager.cs — renders the shared world as spatial panels. // // Subscribes to the XrosClient and turns world-state events into GameObjects: // spawn -> instantiate a panel at the object's pose // move -> reposition an existing panel // remove -> destroy a panel // snapshot -> reconcile the whole scene (on connect) // // This is the "spatial computing" element: panels are anchored in the room // (passthrough) and driven by the shared world model, not by local state. // // Setup: attach to a GameObject, assign the XrosClient and a panelPrefab // (a quad/canvas with a TMP_Text child named "Title"). The prefab's text is // set from the object's properties.title. using System.Collections.Generic; using Newtonsoft.Json.Linq; using TMPro; using UnityEngine; public class WorldPanelManager : MonoBehaviour { public XrosClient client; public GameObject panelPrefab; private readonly Dictionary panels = new Dictionary(); private void OnEnable() { if (client != null) client.OnMessage += HandleMessage; } private void OnDisable() { if (client != null) client.OnMessage -= HandleMessage; } private void HandleMessage(JObject msg) { string op = msg["op"]?.ToString(); switch (op) { case "spawn": Spawn(msg["object"] as JObject); break; case "move": Move(msg["object"] as JObject); break; case "remove": Remove(msg["id"]?.ToString()); break; case "snapshot": foreach (var o in msg["objects"] ?? new JArray()) Spawn(o as JObject); break; } } private void Spawn(JObject obj) { if (obj == null) return; string id = obj["id"]?.ToString(); if (id == null || panels.ContainsKey(id)) return; var go = Instantiate(panelPrefab, transform); go.name = $"panel_{id}"; ApplyPose(go, obj["pose"] as JObject); ApplyTitle(go, obj["properties"] as JObject); panels[id] = go; } private void Move(JObject obj) { if (obj == null) return; string id = obj["id"]?.ToString(); if (id == null || !panels.TryGetValue(id, out var go)) return; ApplyPose(go, obj["pose"] as JObject); } private void Remove(string id) { if (id == null || !panels.TryGetValue(id, out var go)) return; Destroy(go); panels.Remove(id); } private void ApplyPose(GameObject go, JObject pose) { if (pose == null) return; var pos = pose["position"] as JArray; if (pos != null && pos.Count >= 3) go.transform.localPosition = new Vector3( (float)pos[0], (float)pos[1], (float)pos[2]); } private void ApplyTitle(GameObject go, JObject properties) { string title = properties?["title"]?.ToString(); if (string.IsNullOrEmpty(title)) return; var text = go.GetComponentInChildren(); if (text != null) text.text = title; } }