using System; using System.Collections.Generic; using Unity.Collections; using Unity.Jobs; using UnityEngine.XR.ARSubsystems; using Unity.XR.CoreUtils; using ReadOnly = Unity.Collections.ReadOnlyAttribute; namespace UnityEngine.XR.ARFoundation { /// /// A manager for s. Uses the XRPointCloudSubsystem /// to recognize and track point cloud data in the physical environment. /// /// /// Related information: AR Point Cloud Manager component /// [DefaultExecutionOrder(ARUpdateOrder.k_PointCloudManager)] [RequireComponent(typeof(XROrigin))] [DisallowMultipleComponent] [AddComponentMenu("XR/AR Foundation/AR Point Cloud Manager")] [HelpURL("features/point-clouds")] public class ARPointCloudManager : ARTrackableManager< XRPointCloudSubsystem, XRPointCloudSubsystemDescriptor, XRPointCloudSubsystem.Provider, XRPointCloud, ARPointCloud>, IRaycaster { [SerializeField] [Tooltip("If not null, instantiates this prefab for each point cloud.")] GameObject m_PointCloudPrefab; /// /// Getter or setter for the Point Cloud Prefab. /// public GameObject pointCloudPrefab { get => m_PointCloudPrefab; set => m_PointCloudPrefab = value; } /// /// Invoked once per frame with information about the s that have changed, that is, been added, updated, or removed. /// This happens just before s are destroyed, so you can set ARTrackedObject.destroyOnRemoval to false /// from this event to suppress this behavior. /// [Obsolete("pointCloudsChanged has been deprecated in AR Foundation version 6.0. Use trackablesChanged instead.", false)] public event Action pointCloudsChanged; /// /// Invoked when this `MonoBehaviour` is enabled. Used to register with the . /// protected override void OnEnable() { base.OnEnable(); if (subsystem != null) { var raycastManager = GetComponent(); if (raycastManager != null) raycastManager.RegisterRaycaster(this); } } /// /// Invoked when this `MonoBehaviour` is disabled. Used to unregister with the . /// protected override void OnDisable() { base.OnDisable(); var raycastManager = GetComponent(); if (raycastManager != null) raycastManager.UnregisterRaycaster(this); } /// /// The Prefab that will be instantiated for each . Can be `null`. /// /// The Prefab that will be instantiated for each . protected override GameObject GetPrefab() => m_PointCloudPrefab; /// /// The name to be used for the GameObject whenever a new Object is detected. /// protected override string gameObjectName => "ARPointCloud"; /// /// Invoked after each point cloud is updated with new data. /// /// The being updated. /// The new data associated with the point cloud. /// All spatial data is relative to the . protected override void OnAfterSetSessionRelativeData( ARPointCloud pointCloud, XRPointCloud sessionRelativeData) { pointCloud.UpdateData(subsystem); } /// /// Invokes the event. /// /// A list of objects added this frame. /// A list of objects updated this frame. /// A list of objects removed this frame. [Obsolete("OnTrackablesChanged() has been deprecated in AR Foundation version 6.0.", false)] protected override void OnTrackablesChanged( List added, List updated, List removed) { if (pointCloudsChanged != null) { using (new ScopedProfiler("OnPointCloudsChanged")) pointCloudsChanged?.Invoke( new ARPointCloudChangedEventArgs( added, updated, removed)); } } /// /// Implementation for the IRaycaster interface. Raycasts against every point cloud. /// /// A Ray, in session space. /// The type of trackables to raycast against. /// If TrackableType.FeaturePoint is not set, this method returns an empty array. /// The allocator to use for the returned NativeArray. /// A new NativeArray, allocated using , containing /// a list of XRRaycastHits of points hit by the raycast. public NativeArray Raycast( Ray rayInSessionSpace, TrackableType trackableTypeMask, Allocator allocator) { if ((trackableTypeMask & TrackableType.FeaturePoint) == TrackableType.None) return new NativeArray(0, allocator); // TODO: Expose this as a property float raycastAngleInRadians = Mathf.Deg2Rad * 5f; var trackableCollection = trackables; var allHits = new NativeArray(0, allocator); foreach (var pointCloud in trackableCollection) { // Collect the points in the point cloud if (!pointCloud.positions.HasValue) continue; var points = pointCloud.positions.Value; var xform = pointCloud.transform; var sessionSpacePose = new Pose( xform.localPosition, xform.localRotation); var invRotation = Quaternion.Inverse(sessionSpacePose.rotation); // Get the ray in "point cloud space", i.e., relative to the point cloud's local transform var ray = new Ray( invRotation * (rayInSessionSpace.origin - sessionSpacePose.position), invRotation * rayInSessionSpace.direction); // Perform the raycast against each point var infos = new NativeArray(points.Length, Allocator.TempJob); var raycastJob = new PointCloudRaycastJob { points = points, ray = ray, infoOut = infos }; var raycastHandle = raycastJob.Schedule(infos.Length, 1); // Collect the hits using (var hitBuffer = new NativeArray(infos.Length, Allocator.TempJob)) using (infos) using (var count = new NativeArray(1, Allocator.TempJob)) { var collectResultsJob = new PointCloudRaycastCollectResultsJob { points = points, infos = infos, hits = hitBuffer, cosineThreshold = Mathf.Cos(raycastAngleInRadians * .5f), pose = sessionSpacePose, trackableId = pointCloud.trackableId, count = count }; var collectResultsHandle = collectResultsJob.Schedule(raycastHandle); // Wait for it to finish collectResultsHandle.Complete(); // Copy out the results Append(ref allHits, hitBuffer, count[0], allocator); } } return allHits; } static void Append( ref NativeArray currentArray, NativeArray arrayToAppend, int lengthToCopy, Allocator allocator) where T : struct { var dstArray = new NativeArray(currentArray.Length + lengthToCopy, allocator); var currentArrayLength = currentArray.Length; if (currentArrayLength > 0) { NativeArray.Copy(currentArray, dstArray, currentArrayLength); } if (arrayToAppend.Length > 0) { NativeArray.Copy(arrayToAppend, 0, dstArray, currentArrayLength, lengthToCopy); } if (currentArray.IsCreated) { currentArray.Dispose(); } currentArray = dstArray; } struct PointCloudRaycastInfo { public float distance; public float cosineAngleWithRay; } struct PointCloudRaycastJob : IJobParallelFor { [ReadOnly] public NativeSlice points; [WriteOnly] public NativeArray infoOut; [ReadOnly] public Ray ray; public void Execute(int i) { var originToPoint = points[i] - ray.origin; var distance = originToPoint.magnitude; var info = new PointCloudRaycastInfo { distance = distance, cosineAngleWithRay = Vector3.Dot(originToPoint, ray.direction) / distance }; infoOut[i] = info; } } struct PointCloudRaycastCollectResultsJob : IJob { [ReadOnly] public NativeSlice points; [ReadOnly] public NativeArray infos; [WriteOnly] public NativeArray hits; [WriteOnly] public NativeArray count; public float cosineThreshold; public Pose pose; public TrackableId trackableId; public void Execute() { var hitIndex = 0; for (var i = 0; i < points.Length; ++i) { if (infos[i].cosineAngleWithRay >= cosineThreshold) { hits[hitIndex++] = new XRRaycastHit( trackableId, new Pose(pose.rotation * points[i] + pose.position, Quaternion.identity), infos[i].distance, TrackableType.FeaturePoint); } } count[0] = hitIndex; } } } }