using System.Collections.Generic; using Unity.Collections; using UnityEngine.XR.ARSubsystems; #if !UNITY_2019_2_OR_NEWER using UnityEngine.Experimental; #endif 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. /// A concrete struct used to hold data provided by the Subsystem. /// The type of component this component will manage (i.e., create, update, and destroy). [RequireComponent(typeof(ARSessionOrigin))] [DisallowMultipleComponent] public abstract class ARTrackableManager : SubsystemLifecycleManager where TSubsystem : TrackingSubsystem where TSubsystemDescriptor : SubsystemDescriptor where TSessionRelativeData : struct, ITrackable where TTrackable : ARTrackable { /// /// A collection of all trackables managed by this component. /// public TrackableCollection trackables { get { return 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. /// protected virtual GameObject GetPrefab() { return 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(); } /// /// Update is called once per frame. This component's internal state /// is first updated, and then the event is invoked. /// protected virtual void Update() { if (subsystem == null) return; using (var changes = subsystem.GetChanges(Allocator.Temp)) { ClearAndSetCapacity(s_Added, changes.added.Length); foreach (var added in changes.added) s_Added.Add(CreateOrUpdateTrackable(added)); ClearAndSetCapacity(s_Updated, changes.updated.Length); foreach (var updated in changes.updated) s_Updated.Add(CreateOrUpdateTrackable(updated)); ClearAndSetCapacity(s_Removed, changes.removed.Length); foreach (var trackableId in changes.removed) { TTrackable trackable; if (m_Trackables.TryGetValue(trackableId, out trackable)) { m_Trackables.Remove(trackableId); 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 changed, i.e., 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, leaving 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 . /// However, this method does invoke /// on the new trackable. /// /// The trackable will appear in the collection immediately. /// /// /// 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, the trackable with 's /// /// event is invoked, and the trackable's GameObject destroyed if /// is true. /// 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 may 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 may not /// be a removal event if the trackable is added and removed quickly. /// /// If the trackable is not in a pending state, i.e., 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 (and only if it was /// previously reported as "added"). /// However, this method does invoke /// on the trackable. /// /// /// True if the trackable is "pending" (i.e., not yet reported as "added"). protected bool DestroyPendingTrackable(TrackableId trackableId) { TTrackable trackable; if (m_PendingAdds.TryGetValue(trackableId, out 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 CreateGameObject() { var prefab = GetPrefab(); if (prefab == null) { var go = new GameObject(); go.transform.parent = sessionOrigin.trackablesParent; return go; } return Instantiate(prefab, sessionOrigin.trackablesParent); } GameObject CreateGameObject(string name) { var go = CreateGameObject(); go.name = name; return go; } GameObject CreateGameObject(TrackableId trackableId) { return CreateGameObject(GetTrackableName(trackableId)); } TTrackable CreateTrackable(TrackableId trackableId) { var go = CreateGameObject(trackableId); var trackable = go.GetComponent(); if (trackable == null) trackable = go.AddComponent(); return trackable; } TTrackable CreateOrUpdateTrackable(TSessionRelativeData sessionRelativeData) { var trackableId = sessionRelativeData.trackableId; TTrackable trackable; if (m_Trackables.TryGetValue(trackableId, out trackable)) { m_PendingAdds.Remove(trackableId); trackable.pending = false; trackable.SetSessionRelativeData(sessionRelativeData); } else { trackable = CreateTrackable(trackableId); m_Trackables.Add(trackableId, trackable); trackable.SetSessionRelativeData(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(); } }