Combining Multiple Area Targets

Multiple Area Targets made from individual scans can be placed together 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 then 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 whilst 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 a common pose as if one would track the combination of targets as one connected space.

NOTE: In order to add navigation to this type of setup with multiple Area Targets, you can use Unity's Runtime NavMesh Generation instead of the Vuforia NavMesh guide that applies NavMesh to a single Area Target.  

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 choose to use this new mechanism by setting the Requires External Position to TRUE for each Area Target. See the Area Targets in Unity for enabling 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 the latest Vuforia Engine SDK is 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 from an Area Target Capture to have multiple Area Targets with the same origin. Area Targets that were aligned will appear in the Unity scene correctly positioned in relation 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 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 only track the most reliable pose which most likely is 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 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 select 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 pose of the reliable target 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. In a project folder, 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.
/*==============================================================================
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 the ARCamera is moving 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., position not changing) of the Area Targets themselves, it is possible to enforce such behavior by adding the following code:

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);
    }
}

 

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