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