using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.SubsystemsImplementation;
namespace UnityEngine.XR.ARFoundation
{
///
/// A generic manager for components generated by features detected in the physical environment.
///
///
/// When the manager is informed that a trackable has been added, a new GameObject
/// is created with an ARTrackable component on it. If
///
/// is not null, then that Prefab will be instantiated.
///
/// The Subsystem which provides this manager data.
/// The SubsystemDescriptor required to create the Subsystem.
/// The [provider](xref:UnityEngine.SubsystemsImplementation.SubsystemProvider) associated with the .
/// A concrete struct used to hold data provided by the Subsystem.
/// The type of component that this component will manage (that is, create, update, and destroy).
[RequireComponent(typeof(ARSessionOrigin))]
[DisallowMultipleComponent]
public abstract class ARTrackableManager
: SubsystemLifecycleManager
where TSubsystem : TrackingSubsystem, new()
where TSubsystemDescriptor : SubsystemDescriptorWithProvider
where TProvider : SubsystemProvider
where TSessionRelativeData : struct, ITrackable
where TTrackable : ARTrackable
{
internal static ARTrackableManager instance { get; private set; }
///
/// A collection of all trackables managed by this component.
///
public TrackableCollection trackables => new TrackableCollection(m_Trackables);
///
/// Iterates over every instantiated and
/// activates or deactivates its GameObject based on the value of
/// .
/// This calls
/// GameObject.SetActive
/// on each trackable's GameObject.
///
/// If true each trackable's GameObject is activated.
/// Otherwise, it is deactivated.
public void SetTrackablesActive(bool active)
{
foreach (var trackable in trackables)
{
trackable.gameObject.SetActive(active);
}
}
///
/// The ARSessionOrigin which will be used to instantiate detected trackables.
///
protected ARSessionOrigin sessionOrigin { get; private set; }
///
/// The name prefix that should be used when instantiating new GameObjects.
///
protected abstract string gameObjectName { get; }
///
/// The Prefab that should be instantiated when adding a trackable. Can be `null`.
///
/// The prefab should be instantiated when adding a trackable.
protected virtual GameObject GetPrefab() => null;
///
/// A dictionary of all trackables, keyed by TrackableId.
///
protected Dictionary m_Trackables = new Dictionary();
///
/// A dictionary of trackables added via but not yet reported as added.
///
protected Dictionary m_PendingAdds = new Dictionary();
///
/// Invoked by Unity once when this component wakes up.
///
protected virtual void Awake()
{
sessionOrigin = GetComponent();
}
///
protected override void OnEnable()
{
base.OnEnable();
instance = this;
sessionOrigin.trackablesParentTransformChanged += OnTrackablesParentTransformChanged;
}
///
protected override void OnDisable()
{
base.OnDisable();
sessionOrigin.trackablesParentTransformChanged -= OnTrackablesParentTransformChanged;
}
///
/// Determines whether an existing can be added
/// to the underlying subsystem.
///
///
/// If has not been added yet (that is, it is not tracked by this manager) and the
/// manager is either disabled or does not have a valid subsystem, then the 's
/// state is set to `true`.
///
/// An existing to add to
/// the underlying subsystem.
/// Returns `true` if this manager is enabled, has a valid subsystem, and
/// is not already being tracked by this manager. Returns `false` otherwise.
/// Thrown if is `null`.
protected bool CanBeAddedToSubsystem(TTrackable trackable)
{
if (trackable == null)
throw new ArgumentNullException(nameof(trackable));
// If it already has a valid trackableId, then don't re-add it
if (!trackable.trackableId.Equals(TrackableId.invalidId))
return false;
// If we already know about it, then early out
if (m_Trackables.ContainsKey(trackable.trackableId))
return false;
if (!enabled || subsystem == null)
{
// If the manager is disabled or there is no subsystem, and we don't already know about
// this trackable, then it must be pending.
trackable.pending = true;
return false;
}
// Finally, we can only add it if we have a session origin.
return sessionOrigin && sessionOrigin.trackablesParent;
}
void OnTrackablesParentTransformChanged(ARTrackablesParentTransformChangedEventArgs eventArgs)
{
foreach (var trackable in trackables)
{
var transform = trackable.transform;
if (transform.parent != eventArgs.trackablesParent)
{
var desiredPose = eventArgs.trackablesParent.TransformPose(trackable.sessionRelativePose);
transform.SetPositionAndRotation(desiredPose.position, desiredPose.rotation);
}
}
}
///
/// Update is called once per frame. This component's internal state
/// is first updated, and then an event notifying whether any trackables have been added, removed, or updated
/// is invoked by the derived manager.
///
protected virtual void Update()
{
if (subsystem == null || !subsystem.running)
return;
using (new ScopedProfiler("GetChanges"))
using (var changes = subsystem.GetChanges(Allocator.Temp))
{
using (new ScopedProfiler("ProcessAdded"))
{
ClearAndSetCapacity(s_Added, changes.added.Length);
foreach (var added in changes.added)
{
s_Added.Add(CreateOrUpdateTrackable(added));
}
}
using (new ScopedProfiler("ProcessUpdated"))
{
ClearAndSetCapacity(s_Updated, changes.updated.Length);
foreach (var updated in changes.updated)
{
s_Updated.Add(CreateOrUpdateTrackable(updated));
}
}
using (new ScopedProfiler("ProcessRemoved"))
{
ClearAndSetCapacity(s_Removed, changes.removed.Length);
foreach (var trackableId in changes.removed)
{
if (m_Trackables.TryGetValue(trackableId, out var trackable))
{
m_Trackables.Remove(trackableId);
if (trackable)
{
s_Removed.Add(trackable);
}
}
}
}
}
try
{
// User events
if ((s_Added.Count) > 0 ||
(s_Updated.Count) > 0 ||
(s_Removed.Count) > 0)
{
OnTrackablesChanged(s_Added, s_Updated, s_Removed);
}
}
finally
{
// Make sure destroy happens even if a user callback throws an exception
foreach (var removed in s_Removed)
DestroyTrackable(removed);
}
}
///
/// Invoked when trackables have change (that is, they were added, updated, or removed).
/// Use this to perform additional logic, or to invoke public events
/// related to your trackables.
///
/// A list of trackables added this frame.
/// A list of trackables updated this frame.
/// A list of trackables removed this frame.
/// The trackable components are not destroyed until after this method returns.
protected virtual void OnTrackablesChanged(
List added,
List updated,
List removed)
{ }
///
/// Invoked after creating the trackable. The trackable's sessionRelativeData property will already be set.
///
/// The newly created trackable.
protected virtual void OnCreateTrackable(TTrackable trackable)
{ }
///
/// Invoked just after session-relative data has been set on a trackable.
///
/// The trackable that has just been updated.
/// The session relative data used to update the trackable.
protected virtual void OnAfterSetSessionRelativeData(
TTrackable trackable,
TSessionRelativeData sessionRelativeData)
{ }
///
/// Creates a immediately and leaves it in a "pending" state.
///
///
///
/// Trackables are usually created, updated, or destroyed during .
/// This method creates a trackable immediately and marks it as "pending"
/// until it is reported as added by the subsystem. This is useful for subsystems that deal
/// with trackables that can be both detected and manually created.
///
/// This method does not invoke ,
/// so no "added" notifications will occur until the next call to .
///
/// The trackable will appear in the collection immediately.
///
///
/// The data associated with the trackable. All spatial data should
/// be relative to the .
/// A new TTrackable
protected TTrackable CreateTrackableImmediate(TSessionRelativeData sessionRelativeData)
{
var trackable = CreateOrUpdateTrackable(sessionRelativeData);
trackable.pending = true;
m_PendingAdds.Add(trackable.trackableId, trackable);
return trackable;
}
///
/// If in a "pending" state and is
/// `true`, this method destroys the trackable's GameObject. Otherwise, this method has no effect.
///
///
///
/// Trackables are usually removed only when the subsystem reports they
/// have been removed during .
///
/// This method will immediately remove a trackable only if it was created by
///
/// and has not yet been reported as added by the
/// .
///
/// This can happen if the trackable is created and removed within the same frame, as the subsystem might never
/// have a chance to report its existence. Derived classes should use this
/// if they support the concept of manual addition and removal of trackables, as there might not
/// be a removal event if the trackable is added and removed quickly.
///
/// If the trackable is not in a pending state (that is, it has already been reported as "added"),
/// then this method does nothing.
///
///
/// This method does not invoke ,
/// so no "removed" notifications will occur until the next call to . "Removed" notifications will only
/// occur if it was previously reported as "added".
///
///
/// The id of the trackable to destroy.
/// True if the trackable is "pending" (that is, not yet reported as "added").
protected bool DestroyPendingTrackable(TrackableId trackableId)
{
if (m_PendingAdds.TryGetValue(trackableId, out var trackable))
{
m_PendingAdds.Remove(trackableId);
m_Trackables.Remove(trackableId);
DestroyTrackable(trackable);
return true;
}
return false;
}
static void ClearAndSetCapacity(List list, int capacity)
{
list.Clear();
if (list.Capacity < capacity)
list.Capacity = capacity;
}
string GetTrackableName(TrackableId trackableId)
{
return gameObjectName + " " + trackableId.ToString();
}
(GameObject gameObject, bool shouldBeActive) CreateGameObjectDeactivated()
{
var prefab = GetPrefab();
if (prefab == null)
{
var gameObject = new GameObject();
gameObject.SetActive(false);
gameObject.transform.parent = sessionOrigin.trackablesParent;
return (gameObject, true);
}
else
{
var active = prefab.activeSelf;
prefab.SetActive(false);
var gameObject = Instantiate(prefab, sessionOrigin.trackablesParent);
prefab.SetActive(active);
return (gameObject, active);
}
}
(GameObject gameObject, bool shouldBeActive) CreateGameObjectDeactivated(string name)
{
var tuple = CreateGameObjectDeactivated();
tuple.gameObject.name = name;
return tuple;
}
(GameObject gameObject, bool shouldBeActive) CreateGameObjectDeactivated(TrackableId trackableId)
{
using (new ScopedProfiler("CreateGameObject"))
{
return CreateGameObjectDeactivated(GetTrackableName(trackableId));
}
}
TTrackable CreateTrackable(TSessionRelativeData sessionRelativeData)
{
var (gameObject, shouldBeActive) = CreateGameObjectDeactivated(sessionRelativeData.trackableId);
var trackable = gameObject.GetComponent();
if (trackable == null)
{
trackable = gameObject.AddComponent();
}
m_Trackables.Add(sessionRelativeData.trackableId, trackable);
SetSessionRelativeData(trackable, sessionRelativeData);
trackable.gameObject.SetActive(shouldBeActive);
return trackable;
}
void SetSessionRelativeData(TTrackable trackable, TSessionRelativeData data)
{
trackable.SetSessionRelativeData(data);
var worldSpacePose = sessionOrigin.trackablesParent.TransformPose(data.pose);
trackable.transform.SetPositionAndRotation(worldSpacePose.position, worldSpacePose.rotation);
}
///
/// Creates the native counterpart for an existing
/// added with a call to [AddComponent](xref:UnityEngine.GameObject.AddComponent), for example.
///
/// The existing trackable component.
/// The AR data associated with the trackable. This usually comes from the
/// trackable's associated [subsystem](xref:UnityEngine.Subsystem)
protected void CreateTrackableFromExisting(TTrackable existingTrackable, TSessionRelativeData sessionRelativeData)
{
// Same as CreateOrUpdateTrackable
var trackableId = sessionRelativeData.trackableId;
m_Trackables.Add(trackableId, existingTrackable);
SetSessionRelativeData(existingTrackable, sessionRelativeData);
OnCreateTrackable(existingTrackable);
OnAfterSetSessionRelativeData(existingTrackable, sessionRelativeData);
existingTrackable.OnAfterSetSessionRelativeData();
// Remaining logic from CreateTrackableImmediate
m_PendingAdds.Add(trackableId, existingTrackable);
existingTrackable.pending = true;
}
TTrackable CreateOrUpdateTrackable(TSessionRelativeData sessionRelativeData)
{
var trackableId = sessionRelativeData.trackableId;
if (m_Trackables.TryGetValue(trackableId, out var trackable))
{
m_PendingAdds.Remove(trackableId);
trackable.pending = false;
SetSessionRelativeData(trackable, sessionRelativeData);
}
else
{
trackable = CreateTrackable(sessionRelativeData);
OnCreateTrackable(trackable);
}
OnAfterSetSessionRelativeData(trackable, sessionRelativeData);
trackable.OnAfterSetSessionRelativeData();
return trackable;
}
void DestroyTrackable(TTrackable trackable)
{
if (trackable.destroyOnRemoval)
{
Destroy(trackable.gameObject);
}
}
static List s_Added = new List();
static List s_Updated = new List();
static List s_Removed = new List();
}
}