using System; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.XR.ARSubsystems; namespace UnityEngine.XR.ARFoundation { /// /// Represents a face detected by an AR device. /// /// /// Generated by the when an AR device detects /// a face in the environment. /// [DisallowMultipleComponent] [DefaultExecutionOrder(ARUpdateOrder.k_Face)] [HelpURL(typeof(ARFace))] public sealed class ARFace : ARTrackable { /// /// Invoked when the face is updated. If face meshes are supported, there will be /// updated , , , and /// . /// public event Action updated; /// /// The vertices representing the face mesh. Check for existence with vertices.IsCreated. /// This array is parallel to and . Vertices are /// provided in face space, that is, relative to this 's local /// position and rotation. /// public NativeArray vertices => GetUndisposable(m_FaceMesh.vertices); /// /// The normals representing the face mesh. Check for existence with normals.IsCreated. /// This array is parallel to and . /// public unsafe NativeArray normals => GetUndisposable(m_FaceMesh.normals); /// /// The indices defining the triangles of the face mesh. Check for existence with indices.IsCreated. /// The are three times as many indices as triangles, so this will always be a multiple of 3. /// public NativeArray indices => GetUndisposable(m_FaceMesh.indices); /// /// The texture coordinates representing the face mesh. Check for existence with uvs.IsCreated. /// This array is parallel to and . /// public NativeArray uvs => GetUndisposable(m_FaceMesh.uvs); /// /// The [transform](https://docs.unity3d.com/ScriptReference/Transform.html) of the left eye of the face, or `null` if there is no data for the left eye. /// public Transform leftEye { get; private set; } /// /// The [transform](https://docs.unity3d.com/ScriptReference/Transform.html) of the right eye of the face, or `null` if there is no data for the right eye. /// public Transform rightEye { get; private set; } /// /// The [transform](https://docs.unity3d.com/ScriptReference/Transform.html) representing the point where the eyes are fixated or `null` if there is no fixation data. /// public Transform fixationPoint { get; private set; } void Update() { if (m_Updated && updated != null) { updated(new ARFaceUpdatedEventArgs(this)); m_Updated = false; } } void OnDestroy() { m_FaceMesh.Dispose(); } // Creates an alias to the same array, but the caller cannot Dispose it. internal static unsafe NativeArray GetUndisposable(NativeArray disposable) where T : struct { if (!disposable.IsCreated) return default; var undisposable = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray( disposable.GetUnsafePtr(), disposable.Length, Allocator.None); #if ENABLE_UNITY_COLLECTIONS_CHECKS NativeArrayUnsafeUtility.SetAtomicSafetyHandle( ref undisposable, NativeArrayUnsafeUtility.GetAtomicSafetyHandle(disposable)); #endif return undisposable; } internal void UpdateMesh(XRFaceSubsystem subsystem) { subsystem.GetFaceMesh(sessionRelativeData.trackableId, Allocator.Persistent, ref m_FaceMesh); m_Updated = true; } internal void UpdateEyes() { Transform CreateGameObject(string nameOfNewGameObject) { var newTransform = new GameObject(nameOfNewGameObject).transform; newTransform.SetParent(transform, worldPositionStays: false); return newTransform; } if (leftEye == null && rightEye == null && fixationPoint == null) { leftEye = CreateGameObject("Left eye"); rightEye = CreateGameObject("Right eye"); fixationPoint = CreateGameObject("Fixation point"); } UpdateTransformFromPose(leftEye, sessionRelativeData.leftEyePose); UpdateTransformFromPose(rightEye, sessionRelativeData.rightEyePose); fixationPoint.localPosition = sessionRelativeData.fixationPoint; } static void UpdateTransformFromPose(Transform eyeTransform, Pose eyePose) { eyeTransform.localPosition = eyePose.position; eyeTransform.localRotation = eyePose.rotation; } XRFaceMesh m_FaceMesh; bool m_Updated; } }