Combining Multiple Area Targets

Multiple Area Targets from individual scans can be placed in the Unity Editor to create a seamless tracking experience of multiple connected spaces.

It is possible to scan multiple rooms and areas separately and fit them together later to cover a single AR experience over a larger area. However, the Area Targets might drift away from one another at runtime, leaving a gap or incorrect poses of the individual Area Targets.

Incorrect poses of the Area Targets can be seen when only one of multiple Area Targets is in view. The one in view (EXTENDED_TRACKED) will have a correct pose, while the others have no means of adjusting theirs since they are not directly tracked (LIMITED).

The solution is, therefore, to constrain the Area Targets to one another and provide each with a common pose, as if one were tracking the combination of targets as one connected space.

NOTE: To add navigation to this type of setup with multiple Area Targets, you can use Unity’s BuildNavMesh method.

Location Prior

If you have use cases where you hit the limit of Area Target databases that can be activated simultaneously, location prior allows you to activate many more databases at the “cost” of requiring you to provide an external position value. You can use this mechanism by setting the Requires External Position to TRUE for each Area Target. See the Area Targets in Unity to enable the location prior for an Area Target.

With the external position enabled, Vuforia Engine will manage the loading of data from the multiple activated Area Targets and load at runtime the most appropriate ones based on the proximity and overlap of each other. See the Location Prior for details. 

Note that whenever you have multiple active Area Targets in a scene, they must all have the same Requires External Position setting. Dissimilar settings will fail to load some Area Targets.

Setup Unity Project

To follow this guide, ensure that the latest supported Unity version and Vuforia Engine SDK are correctly set up. For a guide to setting up the Vuforia Engine and importing databases, please see our Unity Guide.

  • Import two or more Area Targets that can be fitted together as they are in reality.

Scene Composition

Below is an illustrative example of how three Area Targets were positioned together to form an apartment. Perform the same operation with your Area Targets using the Rotation and Positioning tool in the Unity scene.

TIP: Use the alignment option when generating an Area Target in the Vuforia Creator App or from an Area Target Capture to create multiple Area Targets with the same origin. Aligned Aea Targets will appear in the Unity scene correctly positioned to their position and physical arrangement (provided they are scans of the same connected environment).

  1. Position your Area Targets according to the physical environment.
  2. Create an Empty GameObject and name it MultiArea.
    1. Set the position of the MultiArea to (0, 0, 0).
  3. Drop the Area Targets as a child of MultiArea in the Hierarchy.

  1. Add augmentations normally: As children of the Area Targets. 

MultiArea

To fix the Area Targets together, we need to work with the poses of the targets, ranking them to track only the most reliable pose, which is most likely the area users are situated in at that given time. The below script does the following:

  • Saves the relative pose of each Area Target at the start of runtime.
  • At each frame, it queries the Vuforia State list for active targets to check their tracking status, EXTENDED_TRACKED or LIMITED.
  • Based on the returned tracking status, it ranks and selects the most reliable target pose.
    • If two or more targets are ranked equally, any of them will be used; they will usually be consistent with one another.
  • The script then returns the reliable target’s pose to the MultiArea pose and updates its pose, and consequently, all the Area Targets and child GameObjects.

Add the script to the MultiArea GameObject.

  1. Create a new script with the name MultiArea.cs.
  2. Open the empty script and copy the below code snippet.
  3. Save the file.
  4. Select the MultiArea GameObject and press Add Component in the Inspector.
  5. Select the MultiArea.cs script.
  6. Enable Simulator mode in Play Mode or build to your device to test the experience.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
Copy
/*============================================================================== Copyright (c) 2021, PTC Inc. All rights reserved. Vuforia is a trademark of PTC Inc., registered in the United States and other countries. ==============================================================================*/ using System.Collections.Generic; using UnityEngine; using Vuforia; public class MultiArea : MonoBehaviour { #region PUBLIC_MEMBER_VARIABLES public bool hideAugmentationsWhenNotTracked = true; #endregion PUBLIC_MEMBER_VARIABLES #region PRIVATE_MEMBER_VARS /// <summary> /// Trackable poses relative to the MultiArea root /// </summary> private readonly Dictionary<string, Matrix4x4> mPoses = new Dictionary<string, Matrix4x4>(); private bool m_Tracked = false; #endregion PRIVATE_MEMBER_VARS #region UNITY_MONOBEHAVIOUR_METHODS // Start is called before the first frame update void Start() { var areaTargets = GetComponentsInChildren<AreaTargetBehaviour>(includeInactive: true); foreach (var at in areaTargets) { // Remember the relative pose of each AT to the group root node var matrix = GetFromToMatrix(at.transform, transform); mPoses[at.TargetName] = matrix; Debug.Log("Original pose: " + at.TargetName + "\n" + matrix.ToString("")); // Detach augmentation and re-parent it under the group root node for (int i = at.transform.childCount - 1; i >= 0; i--) { var child = at.transform.GetChild(i); child.SetParent(transform, worldPositionStays: true); } if (hideAugmentationsWhenNotTracked) { ShowAugmentations(false); } } } // Update is called once per frame void Update() { if (!VuforiaApplication.Instance.IsRunning) { return; } // Find current "best tracked" Area Target var atb = GetBestTrackedAreaTarget(); if (!atb) { if (m_Tracked) { m_Tracked = false; if (hideAugmentationsWhenNotTracked) { ShowAugmentations(false); } } return; } if (!m_Tracked) { m_Tracked = true; ShowAugmentations(true); } if (GetGroupPoseFromAreaTarget(atb, out Matrix4x4 groupPose)) { // set new group pose transform.position = groupPose.GetColumn(3); transform.rotation = Quaternion.LookRotation(groupPose.GetColumn(2), groupPose.GetColumn(1)); } } #endregion UNITY_MONOBEHAVIOUR_METHODS #region PRIVATE_METHODS private void ShowAugmentations(bool show) { var renderers = GetComponentsInChildren<Renderer>(); foreach (var rnd in renderers) { rnd.enabled = show; } } private AreaTargetBehaviour GetBestTrackedAreaTarget() { var trackedAreaTargets = GetTrackedAreaTargets(includeLimited: true); if (trackedAreaTargets.Count == 0) { return null; } // look for extended/tracked targets foreach (var at in trackedAreaTargets) { if (at.TargetStatus.Status == Status.TRACKED || at.TargetStatus.Status == Status.EXTENDED_TRACKED) { return at; } } // if no target in EXT/TRACKED was found, // then fallback to any other target // i.e. including LIMITED ones; // just report the first in the list return trackedAreaTargets[0]; } private List<AreaTargetBehaviour> GetTrackedAreaTargets(bool includeLimited = false) { var trackedTargets = new List<AreaTargetBehaviour>(); var activeAreaTargets = FindObjectsOfType<AreaTargetBehaviour>(); foreach (var target in activeAreaTargets) { if (target.enabled && (target.TargetStatus.Status == Status.TRACKED || target.TargetStatus.Status == Status.EXTENDED_TRACKED || (includeLimited && target.TargetStatus.Status == Status.LIMITED))) { trackedTargets.Add(target); } } return trackedTargets; } private bool GetGroupPoseFromAreaTarget(AreaTargetBehaviour atb, out Matrix4x4 groupPose) { groupPose = Matrix4x4.identity; if (mPoses.TryGetValue(atb.TargetName, out Matrix4x4 areaTargetToGroup)) { // Matrix of group root node w.r.t. AT var groupToAreaTarget = areaTargetToGroup.inverse; // Current atb matrix var areaTargetToWorld = atb.transform.localToWorldMatrix; groupPose = areaTargetToWorld * groupToAreaTarget; return true; } return false; } private static Matrix4x4 GetFromToMatrix(Transform from, Transform to) { var m1 = from ? from.localToWorldMatrix : Matrix4x4.identity; var m2 = to ? to.worldToLocalMatrix : Matrix4x4.identity; return m2 * m1; } #endregion PRIVATE_METHODS }

Keeping the Area Targets static in the Unity World

The above approach assumes that the World Center Mode is set to DEVICE and that the ARCamera moves according to the Device Tracking. Therefore, the Area Targets are not guaranteed to remain static in the Unity world since the World Center Mode - DEVICE implies that the target poses of the Area Targets get updated with regard to the AR Camera.

However, if the application requires a perfectly static behavior (i.e., a position not changing) of the Area Targets themselves. In that case, it is possible to enforce such behavior by adding the following code:

123456789101112131415161718192021222324252627
Copy
public bool StaticAreaTargets = true; ///... // Use the LateUpdate() method to bring the group transform back // to the Unity World Center and update the ARCamera pose accordingly void LateUpdate() { if (!VuforiaApplication.Instance.IsRunning) return; // Get the AR Camera var cam = VuforiaBehaviour.Instance.GetComponent<Camera>(); if (!cam) return; if (StaticAreaTargets) { // Temporarily re-parent the AR Camera under this group node // while preserving the relative pose cam.transform.SetParent(transform, worldPositionStays: true); transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); // Un-parent the ARCamera cam.transform.SetParent(null, worldPositionStays: true); } }

 

Learn More

Area Targets in Unity

Can this page be better?
Share your feedback via our issue tracker