/*============================================================================== Copyright (c) 2017-2018 PTC Inc. All Rights Reserved. Confidential and Proprietary - Protected under copyright and other laws. Vuforia is a trademark of PTC Inc., registered in the United States and other countries. ==============================================================================*/ using System.Linq; using UnityEngine; using Vuforia; /// /// A default implementation of Model Reco Event Handler. /// It registers itself at the ModelRecoBehaviour and is notified of new search results. /// public class DefaultModelRecoEventHandler : MonoBehaviour, IObjectRecoEventHandler { #region PRIVATE_MEMBER_VARIABLES private ModelTargetBehaviour mLastRecoModelTarget; private bool mSearching; private float mLastStatusCheckTime; #endregion // PRIVATE_MEMBER_VARIABLES #region PROTECTED_MEMBER_VARIABLES // ModelRecoBehaviour reference to avoid lookups protected ModelRecoBehaviour mModelRecoBehaviour; // Target Finder reference to avoid lookups protected TargetFinder mTargetFinder; #endregion // PROTECTED_MEMBER_VARIABLES #region PUBLIC_VARIABLES /// /// The Model Target used as template when a Model is recognized. /// [Tooltip("The Model Target used as Template when a model is recognized.")] public ModelTargetBehaviour ModelTargetTemplate; /// /// Whether the model should be augmented with a bounding box. /// Only applicable to Template model targets. /// [Tooltip("Whether the model should be augmented with a bounding box.")] public bool ShowBoundingBox; /// /// Can be set in the Unity inspector to display error messages in UI. /// [Tooltip("UI Text label to display model reco errors.")] public UnityEngine.UI.Text ModelRecoErrorText; /// /// Can be set in the Unity inspector to tell Vuforia whether it should: /// - stop searching for new models, once a first model was found, /// or: /// - continue searching for new models, even after a first model was found. /// [Tooltip("Whether Vuforia should stop searching for other models, after the first model was found.")] public bool StopSearchWhenModelFound = false; /// /// Can be set in the Unity inspector to tell Vuforia whether it should: /// - stop searching for new models, while a target is being tracked and is in view, /// or: /// - continue searching for new models, even if a target is currently being tracked. /// [Tooltip("Whether Vuforia should stop searching for other models, while current model is tracked and visible.")] public bool StopSearchWhileTracking = true;//true by default, as this is the recommended behaviour #endregion // PUBLIC_VARIABLES #region UNITY_MONOBEHAVIOUR_METHODS /// /// register for events at the ModelRecoBehaviour /// void Start() { // register this event handler at the model reco behaviour var modelRecoBehaviour = GetComponent(); if (modelRecoBehaviour) { modelRecoBehaviour.RegisterEventHandler(this); } // remember modelRecoBehaviour for later mModelRecoBehaviour = modelRecoBehaviour; } void Update() { if (!VuforiaARController.Instance.HasStarted) return; if (mTargetFinder == null) return; // Check periodically if model target is tracked and in view // The test is not necessary when the search is stopped after first model was found float elapsed = Time.realtimeSinceStartup - mLastStatusCheckTime; if (!StopSearchWhenModelFound && StopSearchWhileTracking && elapsed > 0.5f) { mLastStatusCheckTime = Time.realtimeSinceStartup; if (mSearching) { if (IsModelTrackedInView(mLastRecoModelTarget)) { // Switch Model Reco OFF when model is being tracked/in-view mModelRecoBehaviour.ModelRecoEnabled = false; mSearching = false; } } else { if (!IsModelTrackedInView(mLastRecoModelTarget)) { // Switch Mode Reco ON when no model is tracked/in-view mModelRecoBehaviour.ModelRecoEnabled = true; mSearching = true; } } } } private void OnDestroy() { if (mModelRecoBehaviour != null) { mModelRecoBehaviour.UnregisterEventHandler(this); } mModelRecoBehaviour = null; } #endregion // UNITY_MONOBEHAVIOUR_METHODS #region IModelRecoEventHandler_IMPLEMENTATION /// /// called when TargetFinder has been initialized successfully /// public void OnInitialized(TargetFinder targetFinder) { Debug.Log("ModelReco initialized."); // Keep a reference to the Target Finder mTargetFinder = targetFinder; } /// /// visualize initialization errors /// public void OnInitError(TargetFinder.InitState initError) { // Reset target finder reference mTargetFinder = null; Debug.LogError("Model Reco init error: " + initError.ToString()); ShowErrorMessageInUI(initError.ToString()); } /// /// visualize update errors /// public void OnUpdateError(TargetFinder.UpdateState updateError) { Debug.LogError("Model Reco update error: " + updateError.ToString()); ShowErrorMessageInUI(updateError.ToString()); } /// /// when we start scanning, clear all trackables /// public void OnStateChanged(bool searching) { Debug.Log("ModelReco: state changed: " + (searching ? "searching" : "not searching")); mSearching = searching; if (searching) { // clear all known trackables if (mTargetFinder != null) mTargetFinder.ClearTrackables(false); } } /// /// Handles new search results. /// /// public virtual void OnNewSearchResult(TargetFinder.TargetSearchResult searchResult) { Debug.Log("ModelReco: new search result available: " + searchResult.TargetName); // Find or create the referenced model target GameObject modelTargetGameObj = null; bool builtFromTemplate = false; var existingModelTarget = FindExistingModelTarget((TargetFinder.ModelRecoSearchResult)searchResult); if (existingModelTarget) { modelTargetGameObj = existingModelTarget.gameObject; builtFromTemplate = false; } else if (ModelTargetTemplate) { modelTargetGameObj = Instantiate(ModelTargetTemplate.gameObject); builtFromTemplate = true; } if (!modelTargetGameObj) { Debug.LogError("Could not create a Model Target."); return; } // Enable the new search result as a Model Target ModelTargetBehaviour mtb = mTargetFinder.EnableTracking( searchResult, modelTargetGameObj) as ModelTargetBehaviour; if (mtb) { mLastRecoModelTarget = mtb; // If the model target was created from a template, // we augment it with a bounding box game object if (builtFromTemplate && ShowBoundingBox) { var modelBoundingBox = mtb.ModelTarget.GetBoundingBox(); var bboxGameObj = CreateBoundingBox(mtb.ModelTarget.Name, modelBoundingBox); // Parent the bounding box under the model target. bboxGameObj.transform.SetParent(modelTargetGameObj.transform, false); } if (StopSearchWhenModelFound) { // Stop the target finder mModelRecoBehaviour.ModelRecoEnabled = false; } } } #endregion // IModelRecoEventHandler_IMPLEMENTATION #region PRIVATE_METHODS private ModelTargetBehaviour FindExistingModelTarget(TargetFinder.ModelRecoSearchResult searchResult) { var modelTargetsInScene = Resources.FindObjectsOfTypeAll().ToList().Where(mt => mt.ModelTargetType == ModelTargetType.PREDEFINED).ToArray(); if (modelTargetsInScene == null || modelTargetsInScene.Length == 0) return null; string targetName = searchResult.TargetName; //string targetUniqueId = searchResult.UniqueTargetId; foreach (var mt in modelTargetsInScene) { if (mt.TrackableName == targetName) { mt.gameObject.SetActive(true); return mt; } } return null; } private GameObject CreateBoundingBox(string modelTargetName, OrientedBoundingBox3D bbox) { var bboxGameObj = new GameObject(modelTargetName + "_BoundingBox"); bboxGameObj.transform.localPosition = bbox.Center; bboxGameObj.transform.localRotation = Quaternion.identity; bboxGameObj.transform.localScale = 2 * bbox.HalfExtents; bboxGameObj.AddComponent(); return bboxGameObj; } private void ShowErrorMessageInUI(string text) { if (ModelRecoErrorText) ModelRecoErrorText.text = text; } public static Bounds GetModelTargetWorldBounds(ModelTargetBehaviour mtb) { var bbox = mtb.ModelTarget.GetBoundingBox(); var localCenter = bbox.Center; var localExtents = bbox.HalfExtents; // transform local center to World space var worldCenter = mtb.transform.TransformPoint(localCenter); // transform the local extents to World space var axisX = mtb.transform.TransformVector(localExtents.x, 0, 0); var axisY = mtb.transform.TransformVector(0, localExtents.y, 0); var axisZ = mtb.transform.TransformVector(0, 0, localExtents.z); Vector3 worldExtents = Vector3.zero; worldExtents.x = Mathf.Abs(axisX.x) + Mathf.Abs(axisY.x) + Mathf.Abs(axisZ.x); worldExtents.y = Mathf.Abs(axisX.y) + Mathf.Abs(axisY.y) + Mathf.Abs(axisZ.y); worldExtents.z = Mathf.Abs(axisX.z) + Mathf.Abs(axisY.z) + Mathf.Abs(axisZ.z); return new Bounds { center = worldCenter, extents = worldExtents }; } private bool IsModelTrackedInView(ModelTargetBehaviour modelTarget) { if (!modelTarget) return false; if (modelTarget.CurrentStatus == TrackableBehaviour.Status.NO_POSE) return false; var cam = DigitalEyewearARController.Instance.PrimaryCamera; if (!cam) return false; // Compute the center of the model in World coordinates Bounds modelBounds = GetModelTargetWorldBounds(modelTarget); var frustumPlanes = GeometryUtility.CalculateFrustumPlanes(cam); return GeometryUtility.TestPlanesAABB(frustumPlanes, modelBounds); } #endregion PRIVATE_METHODS #region PUBLIC_METHODS public TargetFinder GetTargetFinder() { return mTargetFinder; } public void ResetModelReco(bool destroyGameObjects) { var objectTracker = TrackerManager.Instance.GetTracker(); if (objectTracker != null) { objectTracker.Stop(); if (mTargetFinder != null) { mTargetFinder.ClearTrackables(destroyGameObjects); mTargetFinder.Stop(); mTargetFinder.StartRecognition(); } else { Debug.LogError("Could not reset TargetFinder"); } objectTracker.Start(); } else { Debug.LogError("Could not reset ObjectTracker"); } } #endregion // PUBLIC_METHODS }