How to Create and Modify Targets in Native

It is possible to create target datasets, which can be loaded, activated, disabled, and unloaded at runtime. Furthermore, an image target can be created from a locally stored image file and added to an empty dataset to generate a trackable for your AR content, all during the application runtime.

To access and modify your targets at runtime, your AR application should access target attributes programmatically using the Trackable class of the Vuforia API. To modify targets at runtime in Unity please refer to the article How to Create and Load Targets in Unity page.

In a native application, 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.

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 a maximum of 1000 Images, Cylinders or MultiTargets is recommended.

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

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

How to Create Targets from Image Files

Applications can use multiple Device Databases at the same time. Databases can be created using the Vuforia Target Manager or by loading image files directly. Targets in databases that are loaded but not activated are not counted as active targets.

The following example allows you to retrieve an image file from a local storage and attach it to a RuntimeImageSource. Hereafter, the runtime image can be loaded into an empty dataset.

// Load and activate a runtime image target at the given path.
bool loadDataSet(std::string imagePath, Vuforia::STORAGE_TYPE storageType, float targetWidthMeters, std::string targetName)
{
	// Request an ObjectTracker instance from the TrackerManager.
	Vuforia::TrackerManager& trackerManager = Vuforia::TrackerManager::getInstance();
        Vuforia::ObjectTracker* objectTracker = static_cast(trackerManager.getTracker(Vuforia::ObjectTracker::getClassType()));

	// Create a new empty data set.    
	Vuforia::DataSet* dataSet = objectTracker->CreateDataSet();

	// Get the runtime image source    
	RuntimeImageSource* runtimeImageSource = objectTracker->getRuntimeImageSource();

	// Load the data set from the given path.    
	if (!runtimeImageSource->setFile(imagePath, storageType, targetWidthMeters, targetName))
	{
		Debug.LogError("Failed to load image file " + imagePath + ".");
		return false;
	}

	// add runtime image target to dataset    
	dataSet->createTrackable(runtimeImageSource);

	// (Optional) Activate the data set.
	objectTracker.ActivateDataSet(dataSet);
	return true;
}

NOTE: alternatively, use setImage() instead of setFile(), if it is a pixel buffer that contains the image

How to Load and Activate Multiple Device Databases at Runtime

The following code snippets demonstrate database loading using the native APIs

Native SDKs

In the example, the application creates and loads two target databases, and then activates one of the databases.

// Get the image tracker:
Vuforia::TrackerManager& trackerManager = Vuforia::TrackerManager::getInstance();
Vuforia::ObjectTracker* objectTracker = static_cast
(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;
}

How to Load Databases at Runtime in Android Projects

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

  1. To load a target using the Vuforia SDK you must copy the two target asset files (.dat and .xml) of the downloaded database file from the assets directory of your project.
  2. Copy the asset files into the appropriate directory to replace the assets in the image targets application:

<DEVELOPMENT_ROOT>\vuforia-sdk-android-xx-yy-zz\samples\project-name\assets\

NOTE: You must do a clean build 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.     

  1. To rebuild your project and initiate the packaging process, select your project, and then go to Project -> Clean... -> Clean selected project below.
  2. In order to use the ObjectTracker to create and load a dataset named myDataset1.xml that was 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(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 runtime.

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 uttons 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 MultiTarget

You can access MultiTargets 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 MultiTarget at Runtime

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

At initialization you can add parts to a MultiTarget 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 target images, compares each target image to a list of names, and then adds them to the MultiTarget. 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 MultiTarget 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 MultiTarget at runtime 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;