using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Unity.XR.OpenXR.Features.PICOSupport; using UnityEngine; using UnityEngine.UI; using UnityEngine.XR; namespace Unity.XR.PXR { [DisallowMultipleComponent] public class PXR_SpatialMeshManager : MonoBehaviour { public GameObject meshPrefab; private Dictionary meshIDToGameobject; private Dictionary spatialMeshNeedingDraw; private Mesh mesh; private XRMeshSubsystem subsystem; static List s_SubsystemsReuse = new List(); private int objectPoolMaxSize = 200; private Queue meshObjectsPool; private const float frameCount = 15.0f; /// /// The drawing of the new spatial mesh is complete. /// public static Action MeshAdded; /// /// The drawing the updated spatial mesh is complete. /// public static Action MeshUpdated; /// /// The deletion of the disappeared spatial mesh is complete. /// public static Action MeshRemoved; void Start() { spatialMeshNeedingDraw = new Dictionary(); meshIDToGameobject = new Dictionary(); meshObjectsPool = new Queue(); InitializePool(); } void Update() { GetXRMeshSubsystem(); if (subsystem != null && subsystem.running) { DrawMesh(); } } void GetXRMeshSubsystem() { if (subsystem != null) return; SubsystemManager.GetSubsystems(s_SubsystemsReuse); if (s_SubsystemsReuse.Count == 0) return; subsystem = s_SubsystemsReuse[0]; PXR_PermissionRequest.RequestUserPermissionMR(Granted => { subsystem.Start(); if (subsystem.running) { OpenXRExtensions.SpatialMeshDataUpdated += SpatialMeshDataUpdated; } }); } void OnEnable() { GetXRMeshSubsystem(); } void OnDisable() { if (subsystem != null && subsystem.running) subsystem.Stop(); } private void InitializePool() { if (meshPrefab != null) { while (meshObjectsPool.Count < objectPoolMaxSize) { GameObject obj = Instantiate(meshPrefab); obj.transform.SetParent(this.transform); obj.SetActive(false); meshObjectsPool.Enqueue(obj); } } } private void DrawMesh() { if (meshPrefab != null) { StartCoroutine(ForeachLoopCoroutine()); } } private IEnumerator ForeachLoopCoroutine() { int totalWork = spatialMeshNeedingDraw.Count; if (totalWork > 0 ) { var meshList = spatialMeshNeedingDraw.Values.ToList(); int workPerFrame = Mathf.CeilToInt(totalWork / frameCount); int currentIndex = 0; while (currentIndex < totalWork) { int workThisFrame = 0; while (workThisFrame < workPerFrame && currentIndex < totalWork) { CreateMeshRoutine(meshList[currentIndex]); currentIndex++; workThisFrame++; } yield return null; } } } void SpatialMeshDataUpdated(List meshInfos) { for (int i = 0; i < meshInfos.Count; i++) { switch (meshInfos[i].state) { case MeshChangeState.Added: { spatialMeshNeedingDraw.Add(meshInfos[i].uuid, meshInfos[i]); } break; case MeshChangeState.Updated: { if (!spatialMeshNeedingDraw.ContainsKey(meshInfos[i].uuid)) { spatialMeshNeedingDraw.Add(meshInfos[i].uuid, meshInfos[i]); } else { spatialMeshNeedingDraw[meshInfos[i].uuid] = meshInfos[i]; } } break; case MeshChangeState.Removed: { MeshRemoved?.Invoke(meshInfos[i].uuid); spatialMeshNeedingDraw.Remove(meshInfos[i].uuid); GameObject removedGo; if (meshIDToGameobject.TryGetValue(meshInfos[i].uuid, out removedGo)) { if (meshObjectsPool.Count < objectPoolMaxSize) { removedGo.SetActive(false); meshObjectsPool.Enqueue(removedGo); } else { Destroy(removedGo); } meshIDToGameobject.Remove(meshInfos[i].uuid); } } break; case MeshChangeState.Unchanged: { spatialMeshNeedingDraw.Remove(meshInfos[i].uuid); } break; default: throw new ArgumentOutOfRangeException(); } } } private void CreateMeshRoutine(PxrSpatialMeshInfo block) { GameObject meshGameObject = GetOrCreateGameObject(block.uuid); var meshFilter = meshGameObject.GetComponentInChildren(); var meshCollider = meshGameObject.GetComponentInChildren(); if (meshFilter.mesh == null) { mesh = new Mesh(); } else { mesh = meshFilter.mesh; mesh.Clear(); } Color[] normalizedColors = new Color[block.vertices.Length]; for (int i = 0; i < block.vertices.Length; i++) { normalizedColors[i] = GetMeshColorBySemanticLabel(block.labels[i]); } mesh.SetVertices(block.vertices); mesh.SetColors(normalizedColors); mesh.SetTriangles(block.indices, 0); meshFilter.mesh = mesh; if (meshCollider != null) { meshCollider.sharedMesh = mesh; } meshGameObject.transform.position = block.position; meshGameObject.transform.rotation = block.rotation; switch (block.state) { case MeshChangeState.Added: { MeshAdded?.Invoke(block.uuid, meshGameObject); } break; case MeshChangeState.Updated: { MeshUpdated?.Invoke(block.uuid, meshGameObject); } break; default: throw new ArgumentOutOfRangeException(); } } GameObject CreateGameObject(Guid meshId) { GameObject meshObject = meshObjectsPool.Dequeue(); meshObject.name = $"Mesh {meshId}"; meshObject.SetActive(true); return meshObject; } GameObject GetOrCreateGameObject(Guid meshId) { GameObject go = null; if (!meshIDToGameobject.TryGetValue(meshId, out go)) { go = CreateGameObject(meshId); meshIDToGameobject[meshId] = go; } return go; } private Color GetMeshColorBySemanticLabel(PxrSemanticLabel label) { switch (label) { case PxrSemanticLabel.Unknown: return Color.white; case PxrSemanticLabel.Floor: return Color.red; case PxrSemanticLabel.Ceiling: return Color.green; case PxrSemanticLabel.Wall: return Color.blue; case PxrSemanticLabel.Door: return Color.grey; case PxrSemanticLabel.Window: return Color.yellow; case PxrSemanticLabel.Opening: return Color.cyan; case PxrSemanticLabel.Table: return Color.magenta; case PxrSemanticLabel.Sofa: return Color.gray; case PxrSemanticLabel.Chair: return new Color(0.8f, 0.2f, 0.5f); case PxrSemanticLabel.Human: return new Color(0.8f, 0.2f, 0.6f); default: return Color.white; } } } }