File size: 3,202 Bytes
eb6aba9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// 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<string, GameObject> panels = new Dictionary<string, GameObject>();

    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<TMP_Text>();
        if (text != null) text.text = title;
    }
}