using System; using System.Collections.Generic; using Unity.Collections; using UnityEngine; using UnityEngine.XR; using UnityEngine.XR.ARFoundation; using UnityEngine.XR.ARSubsystems; #if UNITY_IOS && !UNITY_EDITOR using UnityEngine.XR.ARKit; #endif // UNITY_IOS && !UNITY_EDITOR using Object = UnityEngine.Object; namespace UnityEngine.XR.ARFoundation.Samples { public class MeshClassificationFracking : MonoBehaviour { /// /// The number of mesh classifications detected. /// const int k_NumClassifications = 8; /// /// The mesh manager for the scene. /// public ARMeshManager m_MeshManager; /// /// The mesh prefab for the None classification. /// public MeshFilter m_NoneMeshPrefab; /// /// The mesh prefab for the Wall classification. /// public MeshFilter m_WallMeshPrefab; /// /// The mesh prefab for the Floor classification. /// public MeshFilter m_FloorMeshPrefab; /// /// The mesh prefab for the Ceiling classification. /// public MeshFilter m_CeilingMeshPrefab; /// /// The mesh prefab for the Table classification. /// public MeshFilter m_TableMeshPrefab; /// /// The mesh prefab for the Seat classification. /// public MeshFilter m_SeatMeshPrefab; /// /// The mesh prefab for the Window classification. /// public MeshFilter m_WindowMeshPrefab; /// /// The mesh prefab for the Door classification. /// public MeshFilter m_DoorMeshPrefab; #if UNITY_IOS && !UNITY_EDITOR /// /// A mapping from tracking ID to instantiated mesh filters. /// readonly Dictionary m_MeshFrackingMap = new Dictionary(); /// /// The delegate to call to breakup a mesh. /// Action m_BreakupMeshAction; /// /// The delegate to call to update a mesh. /// Action m_UpdateMeshAction; /// /// The delegate to call to remove a mesh. /// Action m_RemoveMeshAction; /// /// An array to store the triangle vertices of the base mesh. /// readonly List m_BaseTriangles = new List(); /// /// An array to store the triangle vertices of the classified mesh. /// readonly List m_ClassifiedTriangles = new List(); /// /// On awake, set up the mesh filter delegates. /// void Awake() { m_BreakupMeshAction = new Action(BreakupMesh); m_UpdateMeshAction = new Action(UpdateMesh); m_RemoveMeshAction = new Action(RemoveMesh); } /// /// On enable, subscribe to the meshes changed event. /// void OnEnable() { Debug.Assert(m_MeshManager != null, "mesh manager cannot be null"); m_MeshManager.meshesChanged += OnMeshesChanged; } /// /// On disable, unsubscribe from the meshes changed event. /// void OnDisable() { Debug.Assert(m_MeshManager != null, "mesh manager cannot be null"); m_MeshManager.meshesChanged -= OnMeshesChanged; } /// /// When the meshes change, update the scene meshes. /// void OnMeshesChanged(ARMeshesChangedEventArgs args) { if (args.added != null) { args.added.ForEach(m_BreakupMeshAction); } if (args.updated != null) { args.updated.ForEach(m_UpdateMeshAction); } if (args.removed != null) { args.removed.ForEach(m_RemoveMeshAction); } } /// /// Parse the trackable ID from the mesh filter name. /// /// The mesh filter name containing the trackable ID. /// /// The trackable ID parsed from the string. /// TrackableId ExtractTrackableId(string meshFilterName) { string[] nameSplit = meshFilterName.Split(' '); return new TrackableId(nameSplit[1]); } /// /// Given a base mesh, the face classifications for all faces in the mesh, and a single face classification to /// extract, extract into a new mesh only the faces that have the selected face classification. /// /// The original base mesh. /// The array of face classifications for each triangle in the /// /// A single classification to extract the faces from the /// into the /// The output mesh to be updated with the extracted mesh. void ExtractClassifiedMesh(Mesh baseMesh, NativeArray faceClassifications, ARMeshClassification selectedMeshClassification, Mesh classifiedMesh) { // Count the number of faces matching the selected classification. int classifiedFaceCount = 0; for (int i = 0; i < faceClassifications.Length; ++i) { if (faceClassifications[i] == selectedMeshClassification) { ++classifiedFaceCount; } } // Clear the existing mesh. classifiedMesh.Clear(); // If there were matching face classifications, build a new mesh from the base mesh. if (classifiedFaceCount > 0) { baseMesh.GetTriangles(m_BaseTriangles, 0); Debug.Assert(m_BaseTriangles.Count == (faceClassifications.Length * 3), "unexpected mismatch between triangle count and face classification count"); m_ClassifiedTriangles.Clear(); m_ClassifiedTriangles.Capacity = classifiedFaceCount * 3; for (int i = 0; i < faceClassifications.Length; ++i) { if (faceClassifications[i] == selectedMeshClassification) { int baseTriangleIndex = i * 3; m_ClassifiedTriangles.Add(m_BaseTriangles[baseTriangleIndex + 0]); m_ClassifiedTriangles.Add(m_BaseTriangles[baseTriangleIndex + 1]); m_ClassifiedTriangles.Add(m_BaseTriangles[baseTriangleIndex + 2]); } } classifiedMesh.vertices = baseMesh.vertices; classifiedMesh.normals = baseMesh.normals; classifiedMesh.SetTriangles(m_ClassifiedTriangles, 0); } } /// /// Break up a single mesh with multiple face classifications into submeshes, each with an unique and uniform mesh /// classification. /// /// The mesh filter for the base mesh with multiple face classifications. void BreakupMesh(MeshFilter meshFilter) { XRMeshSubsystem meshSubsystem = m_MeshManager.subsystem as XRMeshSubsystem; if (meshSubsystem == null) { return; } var meshId = ExtractTrackableId(meshFilter.name); var faceClassifications = meshSubsystem.GetFaceClassifications(meshId, Allocator.Persistent); if (!faceClassifications.IsCreated) { return; } using (faceClassifications) { if (faceClassifications.Length <= 0) { return; } var parent = meshFilter.transform.parent; MeshFilter[] meshFilters = new MeshFilter[k_NumClassifications]; meshFilters[(int)ARMeshClassification.None] = (m_NoneMeshPrefab == null) ? null : Instantiate(m_NoneMeshPrefab, parent); meshFilters[(int)ARMeshClassification.Wall] = (m_WallMeshPrefab == null) ? null : Instantiate(m_WallMeshPrefab, parent); meshFilters[(int)ARMeshClassification.Floor] = (m_FloorMeshPrefab == null) ? null : Instantiate(m_FloorMeshPrefab, parent); meshFilters[(int)ARMeshClassification.Ceiling] = (m_CeilingMeshPrefab == null) ? null : Instantiate(m_CeilingMeshPrefab, parent); meshFilters[(int)ARMeshClassification.Table] = (m_TableMeshPrefab == null) ? null : Instantiate(m_TableMeshPrefab, parent); meshFilters[(int)ARMeshClassification.Seat] = (m_SeatMeshPrefab == null) ? null : Instantiate(m_SeatMeshPrefab, parent); meshFilters[(int)ARMeshClassification.Window] = (m_WindowMeshPrefab == null) ? null : Instantiate(m_WindowMeshPrefab, parent); meshFilters[(int)ARMeshClassification.Door] = (m_DoorMeshPrefab == null) ? null : Instantiate(m_DoorMeshPrefab, parent); m_MeshFrackingMap[meshId] = meshFilters; var baseMesh = meshFilter.sharedMesh; for (int i = 0; i < k_NumClassifications; ++i) { var classifiedMeshFilter = meshFilters[i]; if (classifiedMeshFilter != null) { var classifiedMesh = classifiedMeshFilter.mesh; ExtractClassifiedMesh(baseMesh, faceClassifications, (ARMeshClassification)i, classifiedMesh); meshFilters[i].mesh = classifiedMesh; } } } } /// /// Update the submeshes corresponding to the single mesh with multiple face classifications into submeshes. /// /// The mesh filter for the base mesh with multiple face classifications. void UpdateMesh(MeshFilter meshFilter) { XRMeshSubsystem meshSubsystem = m_MeshManager.subsystem as XRMeshSubsystem; if (meshSubsystem == null) { return; } var meshId = ExtractTrackableId(meshFilter.name); var faceClassifications = meshSubsystem.GetFaceClassifications(meshId, Allocator.Persistent); if (!faceClassifications.IsCreated) { return; } using (faceClassifications) { if (faceClassifications.Length <= 0) { return; } var meshFilters = m_MeshFrackingMap[meshId]; var baseMesh = meshFilter.sharedMesh; for (int i = 0; i < k_NumClassifications; ++i) { var classifiedMeshFilter = meshFilters[i]; if (classifiedMeshFilter != null) { var classifiedMesh = classifiedMeshFilter.mesh; ExtractClassifiedMesh(baseMesh, faceClassifications, (ARMeshClassification)i, classifiedMesh); meshFilters[i].mesh = classifiedMesh; } } } } /// /// Remove the submeshes corresponding to the single mesh. /// /// The mesh filter for the base mesh with multiple face classifications. void RemoveMesh(MeshFilter meshFilter) { var meshId = ExtractTrackableId(meshFilter.name); var meshFilters = m_MeshFrackingMap[meshId]; for (int i = 0; i < k_NumClassifications; ++i) { var classifiedMeshFilter = meshFilters[i]; if (classifiedMeshFilter != null) { Object.Destroy(classifiedMeshFilter); } } m_MeshFrackingMap.Remove(meshId); } #endif // UNITY_IOS && !UNITY_EDITOR } }