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