using System; using System.Collections.Generic; using Unity.Collections; using UnityEngine.XR.ARSubsystems; using Unity.XR.CoreUtils; using Unity.XR.CoreUtils.Collections; using UnityEngine.Events; using UnityEngine.SubsystemsImplementation; namespace UnityEngine.XR.ARFoundation { /// /// A base class for trackable managers. Trackable managers use data from tracking subsystems to create and maintain /// trackable components and their GameObjects. Refer to /// [Trackables and trackable managers](xref:arfoundation-managers#trackables-and-trackable-managers) for more information. /// /// The tracking subsystem type. /// The subsystem descriptor type. /// The subsystem provider type. /// The subsystem data type. /// The type of component that this component will manage (that is, create, update, and destroy). [RequireComponent(typeof(XROrigin))] [DisallowMultipleComponent] public abstract class ARTrackableManager : SubsystemLifecycleManager, ITrackablesChanged where TSubsystem : TrackingSubsystem, new() where TSubsystemDescriptor : SubsystemDescriptorWithProvider where TProvider : SubsystemProvider where TSessionRelativeData : struct, ITrackable where TTrackable : ARTrackable { static List s_Added = new(); static List s_Updated = new(); // The deprecated trackable manager APIs ARPlaneManager.planesChanged, ARAnchorManager.anchorsChanged, etc, // require a list of TTrackable to be served to the user. Once these deprecated APIs are removed, this // List should be removed as well. Until then, removed trackables are doubly saved in // different formats. static List s_Removed = new(); static List> s_RemovedTrackables = new(); static ReadOnlyList s_AddedReadOnly = new(s_Added); static ReadOnlyList s_UpdatedReadOnly = new(s_Updated); static ReadOnlyList> s_RemovedReadOnly = new(s_RemovedTrackables); /// /// Invoked when trackables have changed (been added, updated, or removed). /// [field: SerializeField] public UnityEvent> trackablesChanged { get; private set; } = new(); /// /// A dictionary of all trackables keyed by TrackableId. /// protected Dictionary m_Trackables = new(); /// /// A dictionary of trackables added via but not /// yet reported as added. /// protected Dictionary m_PendingAdds = new(); /// /// The XR Origin component that will be used to instantiate detected trackables. /// protected XROrigin origin { get; private set; } /// /// The name prefix that should be used when instantiating new GameObjects. /// protected abstract string gameObjectName { get; } internal static ARTrackableManager instance { get; private set; } /// /// A collection of all trackables managed by this component. /// public TrackableCollection trackables => new(m_Trackables); /// /// 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; /// /// Saves a reference to the XR Origin component. /// protected virtual void Awake() { origin = GetComponent(); } /// protected override void OnEnable() { base.OnEnable(); instance = this; origin.TrackablesParentTransformChanged += OnTrackablesParentTransformChanged; } /// protected override void OnDisable() { base.OnDisable(); origin.TrackablesParentTransformChanged -= OnTrackablesParentTransformChanged; } void OnTrackablesParentTransformChanged(ARTrackablesParentTransformChangedEventArgs eventArgs) { foreach (var trackable in trackables) { var trackableTransform = trackable.transform; if (trackableTransform.parent != eventArgs.TrackablesParent) { var desiredPose = eventArgs.TrackablesParent.TransformPose(trackable.pose); trackableTransform.SetPositionAndRotation(desiredPose.position, desiredPose.rotation); } } } /// /// Iterates over every instantiated and /// activates or deactivates its GameObject based on the value of . /// /// If , each trackable's GameObject is activated. /// Otherwise, they are deactivated. public void SetTrackablesActive(bool active) { foreach (var trackable in trackables) { trackable.gameObject.SetActive(active); } } /// /// Determines whether an existing can be added /// to the underlying subsystem. /// /// /// If has not been reported as added yet, and this component is either disabled or /// does not have a valid subsystem, then the 's /// state is set to . /// /// An existing trackable to add to the subsystem. /// Returns if this manager is enabled, has a valid subsystem, and /// is not already being tracked by this manager. /// Otherwise, returns otherwise. /// Thrown if is . /// 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 XR origin. return origin && origin.TrackablesParent; } /// /// Update is called once per frame. This component first updates its internal state, then invokes the /// [trackablesChanged](xref:UnityEngine.XR.ARFoundation.ARTrackableManager`5.trackablesChanged) event. /// 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")) { s_Added.Clear(); foreach (var added in changes.added) { s_Added.Add(CreateOrUpdateTrackable(added)); } } using (new ScopedProfiler("ProcessUpdated")) { s_Updated.Clear(); foreach (var updated in changes.updated) { s_Updated.Add(CreateOrUpdateTrackable(updated)); } } using (new ScopedProfiler("ProcessRemoved")) { s_RemovedTrackables.Clear(); s_Removed.Clear(); foreach (var trackableId in changes.removed) { if (m_Trackables.Remove(trackableId, out var trackable)) { if (trackable == null) { s_RemovedTrackables.Add(new KeyValuePair( trackableId, null)); } else { s_Removed.Add(trackable); s_RemovedTrackables.Add(new KeyValuePair( trackableId, trackable)); } } } } } try { // User events bool addedOrUpdated = s_Added.Count > 0 || s_Updated.Count > 0; if (addedOrUpdated || s_Removed.Count > 0) { #pragma warning disable CS0618 // disables warning from deprecation OnTrackablesChanged(s_Added, s_Updated, s_Removed); #pragma warning restore CS0618 } if (addedOrUpdated || s_RemovedTrackables.Count > 0) { trackablesChanged?.Invoke(new ARTrackablesChangedEventArgs( s_AddedReadOnly, s_UpdatedReadOnly, s_RemovedReadOnly)); } } finally { // Make sure destroy happens even if a user callback throws an exception foreach (var removed in s_Removed) AfterTrackableRemoved(removed); } } /// /// Invoked when trackables have changed (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. [Obsolete("OnTrackablesChanged() has been deprecated in AR Foundation version 6.0.", false)] protected virtual void OnTrackablesChanged( List added, List updated, List removed) { } /// /// Invoked after creating the trackable. The trackable's /// [sessionRelativeData](xref:UnityEngine.XR.ARFoundation.ARTrackable`2.sessionRelativeData) 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 in a "pending" state. /// The trackable will appear in the collection immediately, but will not be reported /// as added until the subsystem successfully adds it. This is useful for subsystems that deal /// with trackables that can be both automatically detected and manually created. /// /// The data associated with the trackable. /// A new trackable. protected TTrackable CreateTrackableImmediate(TSessionRelativeData sessionRelativeData) { var trackable = CreateOrUpdateTrackable(sessionRelativeData); trackable.pending = true; m_PendingAdds.Add(trackable.trackableId, trackable); return trackable; } /// /// If the trackable with id is in a "pending" state, and /// is , /// this method destroys the trackable's GameObject. Otherwise, this method has no effect. /// /// /// /// 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. /// /// /// The id of the trackable to destroy. /// if the trackable was "pending" and is now destroyed. /// Otherwise, . protected bool DestroyPendingTrackable(TrackableId trackableId) { if (m_PendingAdds.TryGetValue(trackableId, out var trackable)) { m_PendingAdds.Remove(trackableId); m_Trackables.Remove(trackableId); AfterTrackableRemoved(trackable); return true; } return false; } /// /// 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. 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; } string GetTrackableName(TrackableId trackableId) { return gameObjectName + " " + trackableId; } (GameObject gameObject, bool shouldBeActive) CreateGameObjectDeactivated() { var prefab = GetPrefab(); if (prefab == null) { var newGameObject = new GameObject(); newGameObject.SetActive(false); newGameObject.transform.parent = origin.TrackablesParent; return (newGameObject, true); } var active = prefab.activeSelf; prefab.SetActive(false); var prefabInstance = Instantiate(prefab, origin.TrackablesParent); prefab.SetActive(active); return (prefabInstance, 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 (trackableGameObject, shouldBeActive) = CreateGameObjectDeactivated(sessionRelativeData.trackableId); var trackable = trackableGameObject.GetComponent(); if (trackable == null) { trackable = trackableGameObject.AddComponent(); } m_Trackables.Add(sessionRelativeData.trackableId, trackable); SetSessionRelativeData(trackable, sessionRelativeData); trackableGameObject.SetActive(shouldBeActive); return trackable; } void SetSessionRelativeData(TTrackable trackable, TSessionRelativeData data) { trackable.SetSessionRelativeData(data); var worldSpacePose = origin.TrackablesParent.TransformPose(data.pose); trackable.transform.SetPositionAndRotation(worldSpacePose.position, worldSpacePose.rotation); } 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; } static void AfterTrackableRemoved(TTrackable trackable) { if (trackable.destroyOnRemoval) { Destroy(trackable.gameObject); } } } }