How To Access and Modify Targets at Run Time

Target datasets can be loaded, activated, disabled, and unloaded at run time. Your AR application can access target attributes programmatically using the Trackable class of the Vuforia API.

You will use the Vuforia::UpdateCallback::Vuforia_onUpdate() callback. This function is called when the SDK is finished with a camera frame and it is safe to do a reconfiguration.

The following sections cover target management tasks that you can perform at run time.

 


How To Load and Activate Multiple Device Databases at Runtime

Applications can use multiple Device Databases at the same time. Targets in databases that are loaded but not activated are not counted as active targets. A maximum of 20 Object Targets is supported, and maximum of 1000 Image, Cylinder or MultiTargets is recommended.

If you use Object Targets with these other target types, you can use a maximum of 20 Object Targets and 80 of the other target types.

The following code snippets demonstrate database loading using the native and Unity APIs:

Native SDKs

In the following example, the application creates and loads two target databases, and then activates one of the databases (from the ImageTargets example).

    // Get the image tracker:
    Vuforia::TrackerManager& trackerManager = Vuforia::TrackerManager::getInstance();
    Vuforia::ObjectTracker* objectTracker = static_cast<Vuforia::ObjectTracker*>
        (trackerManager.getTracker(Vuforia::ObjectTracker::getClassType()));

    if (objectTracker == NULL)
    {
        LOG("Failed to load tracking data set because the ObjectTracker has not been initialized.");
        return 0;
    }

    // Create the data sets:
    dataSetStonesAndChips = objectTracker->createDataSet();

    if (dataSetStonesAndChips == 0)
    {
        LOG("Failed to create a new tracking data.");
        return 0;
    }

    dataSetTarmac = imageTracker->createDataSet();

    if (dataSetTarmac == 0)
    {
        LOG("Failed to create a new tracking data.");
        return 0;
    }

    // Load the data sets:
    if (!dataSetStonesAndChips->load("StonesAndChips.xml", Vuforia::DataSet::STORAGE_APPRESOURCE))
    {
        LOG("Failed to load data set.");
        return 0;
    }

    if (!dataSetTarmac->load("Tarmac.xml", Vuforia::DataSet::STORAGE_APPRESOURCE))
    {
        LOG("Failed to load data set.");
        return 0;
    }

    // Activate the data set:
    if (!objectTracker->activateDataSet(dataSetStonesAndChips))
    {
        LOG("Failed to activate data set.");
        return 0;
    }

Unity Extension

Three principal classes are used to load/unload and activate/deactivate databases at runtime:

Loading a database

Note: You can also call "Exists" without specifying a storage type. The assumption, then, is that your devce database has been bundled with the application in StreamingAssets/QCAR.

  1. Check whether the file at the given storage location exists using DataSet.Exists()
  2. Request an ObjectTracker instance from TrackerManager
  3. Create a new DataSet instance using ObjectTracker.CreateDataSet()
  4. Load a dataset file from its storage location using DataSet.Load()

The same storage type rules apply as for Exists .

Activating a database

Use the objectTracker instance to activate a dataset by calling ObjectTracker.ActivateDataSet(DataSet dataset);

// Load and activate a data set at the given path.
private bool LoadDataSet(string dataSetPath, DataSet.StorageType storageType)
{
    // Check if the data set exists at the given path.
    if (!DataSet.Exists(dataSetPath, storageType))
    {
        Debug.LogError("Data set " + dataSetPath + " does not exist.");
        return false;
    }
  
    // Request an ImageTracker instance from the TrackerManager.
    ObjectTracker objectTracker = TrackerManager.Instance.GetTracker();
  
    // Create a new empty data set.
    DataSet dataSet = objectTracker.CreateDataSet();
  
    // Load the data set from the given path.
    if (!dataSet.Load(dataSetPath, storageType))
    {
        Debug.LogError("Failed to load data set " + dataSetPath + ".");
        return false;
    }
  
    // (Optional) Activate the data set.
    objectTracker.ActivateDataSet(dataSet);
         
    return true;
}

 


How To Load Databases at Runtime in Android and iOS Projects

The following section provides instructions and code samples showing how to load a Device Database in an Android or iOS project.

  1. To load a target using the Vuforia SDK you must copy the two target asset files of the downloaded database file from the assets directory of your project.
  2. After downloading the Dataset Configuration XML, you can modify it to add virtual buttons or to define multi-targets.
  3. Copy the asset files into the appropriate directory to replace the assets in the image targets application:
ANDROID:
<DEVELOPMENT_ROOT>\vuforia-sdk-android-xx-yy-zz\samples\ImageTargets\assets

iOS:
<DEVELOPMENT_ROOT>/vuforia-sdk-ios-xx-yy-zz/samples/ImageTargets/media

Note: ANDROID: You must do a clean build in Eclipse after the assets have been modified in the directory. Otherwise, the packager does not include the new assets in your app installer APK package.

  1. To rebuild your project and initiate the packaging process, select your project and then go to Project > Clean... > Clean selected project below.
  2. ANDROID: Given an existing ObjectTracker to create and load a dataset named myDataset1.xml that is placed in the assets directory, you would make the following C++ call:
Vuforia::DataSet* myDataset = 0;
myDataset = objectTracker->createDataSet();
if (myDataset == 0)
{
    LOG("Failed to create a new tracking data.");
    return 0;
}
if (!myDataset->load("myDataset1.xml", Vuforia::DataSet::STORAGE_APPRESOURCE))
{
    LOG("Failed to load data set.");
    return 0;
}
  1. iOS: Datasets placed in the assets folder are packaged by the sample Xcode projects into the root of the app bundle. Given an existing ObjectTracker to create and load a dataset named myDataset1.xml that is present in the root of the bundle, you would make the following Objective-C call:
- (Vuforia::DataSet *)loadDataSet:(NSString *)dataSetPath
{
    Vuforia::DataSet *theDataSet = nil;
    
    const char* msg;
    const char* msgNotInit = "Failed to load tracking data set because the ImageTracker has not been initialized.";
    const char* msgFailedToCreate = "Failed to create a new tracking data.";
    const char* msgFailedToLoad = "Failed to load data set.";

    // Get the image tracker:
    Vuforia::TrackerManager& trackerManager = Vuforia::TrackerManager::getInstance();
    Vuforia::ObjectTracker* objectTracker = static_cast<Vuforia::ObjectTracker*>(trackerManager.getTracker(Vuforia::Tracker::OBJECT_TRACKER));

    if (objectTracker == NULL)
    {
        msg = msgNotInit;
        errorCode = VUFORIA_ERRCODE_INIT_TRACKER;
    }
    else
    {
        // Create the data sets:
        theDataSet = objectTracker->createDataSet();
        if (theDataSet == nil)
        {
            msg = msgFailedToCreate;
            errorCode = VUFORIA_ERRCODE_CREATE_DATASET;
        }
        else
        {
            // Load the data set from the App Bundle
            // If the DataSet were in the Documents folder 
            // we'd use STORAGE_ABSOLUTE and the full path
            if (!theDataSet->load([dataSetPath cStringUsingEncoding:NSASCIIStringEncoding],
                Vuforia::DataSet::STORAGE_APPRESOURCE))
            {
                msg = msgFailedToLoad;
                errorCode = VUFORIA_ERRCODE_LOAD_DATASET;
                objectTracker->destroyDataSet(theDataSet);
                theDataSet = nil;
            }
            else
            {
                NSLog(@"Successfully loaded data set.");
            }
        }
    }
    return theDataSet;
}

 


How To Check a Trackable's State Using the Android SDK

This section covers a sample target management task that you can perform at run time.

Image targets that are detected and tracked in the current frame can be accessed through a list of TrackableResult objects. In the following example, the code does the following:

  1. Iterates through the trackable results of a state object
  2. Gets the pose information of each target
  3. Modifies the rendering texture based on the target name.

See:
How To Use the Trackable Base Class

Note: The actual rendering code is not shown.

// Did we find any trackables this frame?
for(int tIdx = 0; tIdx < state.getNumTrackableResults(); tIdx++)
{
    // Get the trackable result:
    const Vuforia::TrackableResult* result = state.getTrackableResult(tIdx);
    const Vuforia::Trackable & trackable = result->getTrackable ();
    Vuforia::Matrix44F modelViewMatrix = Vuforia::Tool::convertPose2GLMatrix(result->getPose()); 
 
    // Choose the texture based on the target name:
    int textureIndex;
    if (strcmp(trackable.getName(), "stones") == 0) 
        textureIndex = 0;
    else if (strcmp(trackable.getName(), "chips") == 0)
        textureIndex = 1;
    else
        textureIndex = 2;
    const Texture* const thisTexture = textures[textureIndex];
 
    // ... render actual geometry
    // ... 
}

 


How To Create and Destroy Virtual Buttons at Runtime

This section provides sample code showing how to access, create and destroy Virtual Buttons programmatically at runtime.

Request virtual button state

You can request the virtual button state from active targets in the scene by iterating through the button child objects. The following code snipped provides an example of this action.

// Iterate through this target virtual buttons:
for (int i = 0; i < targetResult->getNumVirtualButtons(); ++i)
{
    const VirtualButtonResult* button = targetResult->getVirtualButtonResult(i);
    // If the button is pressed, then use this texture:
    if (buttonResult->isPressed())
    {
        textureIndex = i+1;
        break;
    }
}

Object receives update callbacks from the Vuforia SDK

To create or destroy virtual buttons dynamically, you should make these changes after the tracker updates the state. After the tracker delivers a valid state, the Vuforia SDK calls this registered callback on each frame, as illustrated in the following code snippet.

// Object to receive update callbacks from Vuforia SDK
class VirtualButton_UpdateCallback : public Vuforia::UpdateCallback
{    
    // Update runs in the tracking thread therefore it is guaranteed that the tracker is
    // not doing anything at this point. => Reconfiguration is possible.
    virtual void Vuforia_onUpdate(Vuforia::State& /*state*/)
    {
        if (updateBtns)
        {
            // Collect the references to the imageTarget
            ...
            if (buttonMask & BUTTON_1)
            {
                toggleVirtualButton(imageTarget, virtualButtonColors[0],
                                    -108.68f, -53.52f, -75.75f, -65.87f);
            }
            if (buttonMask & BUTTON_2)
            {
                ...
            }
            ...
            buttonMask = 0;
            updateBtns = false;
        }
    }
} vuforiaUpdate;

Create or destroy virtual buttons

The following function executes the actual toggle of the button. This function is called for each button in the list that is created in the previous code fragment. The following code fragment shows how to create a new button and how an existing button can be accessed or destroyed.

// Create/destroy a Virtual Button at runtime
//
// Note: This will NOT work if the tracker is active!
bool toggleVirtualButton(Vuforia::ImageTarget* imageTarget, const char* name,
                    float left, float top, float right, float bottom)
{
    bool buttonToggleSuccess = false;
   
    Vuforia::VirtualButton* virtualButton = imageTarget->getVirtualButton(name);
    if (virtualButton != NULL)
    {
        // Destroying Virtual Button
        buttonToggleSuccess = imageTarget->destroyVirtualButton(virtualButton);
    }
    else
    {
        // Creating Virtual Button
        Vuforia::Rectangle vbRectangle(left, top, right, bottom);
        Vuforia::VirtualButton* virtualButton = imageTarget->createVirtualButton(name, vbRectangle);
        if (virtualButton != NULL)
            buttonToggleSuccess = true;
    }
   
    return buttonToggleSuccess;
}

 


How To Get Pose Information for a Multi-Target

You can access multi-targets using a list of active targets. In the following example, the code does the following:

  1. Accesses the first element in the TrackableResult list of a State object
  2. Checks the type of target
  3. Gets the pose information of the target
// Did we find any trackables this frame?
if (state.getNumTrackableResults())
{
    // Get the trackable:
    const Vuforia::TrackableResult* result = state.getTrackableResult(0);
    const Vuforia::Trackable& trackable = result.getTrackable ();

    assert(trackable.getType()== Vuforia::Trackable::MULTI_TARGET);

    Vuforia::Matrix44F modelViewMatrix = 
      Vuforia::Tool::convertPose2GLMatrix(result->getPose());        
 
    // ... render actual geometry
    // ...
}

 


How To Add Images to a Multi Target at Runtime

This section covers a sample target management task that you can perform at run time.

At initialization you can add parts to a multi-target and also set the spatial relationship between them. This action is shown in the following snippet of code.

The code cycles through the list of available image targets, compares each image target to a list of names, and then adds them to the multi-target. The transformation data is updated from an array. The sample code also shows how to use the tool functions to create vectors, to set translation and rotation of the transformation matrix, and to apply the transformation.

It is important to note that this step happens after the Tracker is initialized, but before the Tracker is started, with Vuforia::Tracker::getInstance().start().

// Try to find each ImageTarget. If we find it, this actually means that it
// is not part of the MultiTarget yet: ImageTargets that are part of a
// MultiTarget don't show up in the list of Trackables.
// Each ImageTarget that we find, is then made a part of the
// MultiTarget and a pose is set for it).
 
int numAdded = 0;
for(int i=0; i<6; i++)
{
    if(Vuforia::ImageTarget* it = findImageTarget(names[i]))
    {
        int idx = mit->addPart(it);
        Vuforia::Vec3F t(trans+i*3),a(rots+i*4);
        Vuforia::Matrix34F mat;
        Vuforia::Tool::setTranslation(mat, t);
        Vuforia::Tool::setRotation(mat, a, rots[i*4+3]);
        mit->setPartOffset(idx, mat);
        numAdded++;
    }
}

Changes to a multi-target can also be applied while the tracker is running. It is important that this change happens after the tracker has updated the State object. As shown in the previous code snippet, the correct method is to use the Vuforia::UpdateCallback::Vuforia_onUpdate() interface.

The following code fragment illustrates a registered callback that removes one part of a multi-target at run time without altering the state:

// The following call-back removes the bottom (idx=5) part of the
// Flakes box at run-time. The first time this is executed, it will actually
// work. After that the box has only five parts and the call will be
// ignored (returning false).

struct MyUpdateCallBack : public Vuforia::UpdateCallback
{
    virtual void Vuforia_onUpdate(Vuforia::State& state)
    {
        if(mit!=NULL)
        {
            mit->removePart(5);
        }
    }
} myUpdateCallBack;