iOS Marker to Markerless

This tutorial takes you through to transfer a node that has been added to an image trackable to ArbiTrack.

Advanced Tutorial

This tutorial assumes you have gone through both Marker Basics and ArbiTrack Basics. If you have not, it is strongly recommended that you do so before you head any further.

This tutorial uses the Lego Marker and Kudan Cow assets from the Marker Basics tutorial. If you don’t already have them, head over to Marker Basics and click the download link.

Create an image trackable

First things first, you’ll need to create an image trackable using the Lego Marker image, then create an image node with the Kudan Cow image and add it to the image trackable.

You’ll also need to add an extra line of code here to make sure our image node has a unique name. We’ll use this name to find it in the tracker later.

ViewController.m
- (void)touchesBegan:(NSSet<UITouch *> *)// Initialise the image node with our image.
// We don't include the file extension for this image because it's a PNG.
ARImageNode *imageNode = [[ARImageNode alloc] initWithBundledFile:@"Kudan Cow"];

imageNode.name = @"Cow";

Register the View Controller as an ArbiTrack delegate

One of the things we need to know is when ArbiTrack starts. Fortunately, we have a delegate for that. Add the following code at the end of your setupContent method:

ViewController.m
- (void)touchesBegan:(NSSet<UITouch *> *)// Initialise ArbiTrack
ARArbiTrackerManager *arbiTrack = [ARArbiTrackerManager getInstance];
[arbiTrack initialise];
 
//Add view controller as an ArbiTrack delegate
[arbiTrack addDelegate:self];

This will call a delegate method – arbiTrackStarted – when ArbiTrack has started and positioned its world.

Setup the target node

We’ll be using the target node a little differently here. In the ArbiTrack Basics tutorial, we created a node and used it as a target. In this tutorial, we want ArbiTrack to start tracking at the same position as the image trackable. Therefore, we must use the trackable’s world as ArbiTrack’s target node. Add the following code to your setupContent method:

ViewController.m
// Use the image trackable's world as the target node.
// This causes ArbiTrack to start tracking at the trackable's position.
arbiTrack.targetNode = imageTrackable.world;

Implement ArbiTrack’s delegate method

The next thing we need to do is tell ArbiTrack what it needs to do when it starts. To do this, we will implement the arbiTrackStarted delegate method in our View Controller. It looks like this:

ViewController.m
// Delegate method called once ArbiTrack has started
- (void)arbiTrackStarted
{
  
}

To switch between marker and markerless tracking, we’ll need to get the image node that appears when the marker is detected and transfer it to ArbiTrack’s world. We also need to make sure that the image is in the right position and orientation, otherwise it will look like it jumps around when it changes between tracking types, and we don’t want that.

Add the following code to your arbiTrackStarted method:

ViewController.m
// Delegate method called once ArbiTrack has started
- (void)arbiTrackStarted
{
  // Find the lego trackable in the tracker using it's name.
  ARImageTrackable *imageTrackable = [[ARImageTrackerManager getInstance] findTrackableByName:@"Lego Marker"];
    
  // Find the cow node in the lego trackable's world using it's name.
  ARImageNode *cowNode = (ARImageNode *)[imageTrackable.world findChildWithName:@"Cow"];
  
  ARArbiTrackerManager *arbiTrack = [ARArbiTrackerManager getInstance];
    
  // Alter the orientation of the cow node to preserve it's full orientation as it's reparented.
    cowNode.orientation = [arbiTrack.world.orientation.inverse multiplyByQuaternion:cowNode.fullOrientation];
    
  // Remove the cow node as a child of the trackable world and add it to ArbiTrack's world.
  [imageTrackable.world removeChild:cowNode];
  [arbiTrack.world addChild:cowNode];
}

Now, when ArbiTrack starts, it will reparent the image node so that it is tracked markerlessly. We still need to tell ArbiTrack when to start, though.

Start ArbiTrack

To tell ArbiTrack when to start, we’ll use the same method as the one in the ArbiTrack Basics tutorial to respond to touch input. It looks like this:

ViewController.m
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
  
}

Now, we need to know whether or not ArbiTrack is running when we tap, because we only want ArbiTrack to start if it hasn’t already started. In addition, we want to know whether or not our trackable is being detected, because we can’t go from marker tracking to markerless tracking if we aren’t tracking a marker.

Implementing these steps, our touchesBegan method now looks like this:

Objective-C
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
  // Get references to the ArbiTracker and Image Trackable.
  ARArbiTrackerManager *arbiTrack = [ARArbiTrackerManager getInstance];
  ARImageTrackable *imageTrackable = [[ARImageTrackerManager getInstance] findTrackableByName:@"Lego Marker"];
  
  // Only start ArbiTrack if it isn't currently running.
  if (!arbiTrack.isTracking)
  {
      // If the marker is not currently detected, exit the method and don't switch to arbitrack.
      if (!imageTrackable.isDetected)
      {
          return;
      }

      [arbiTrack start];
  }
}

This means that when we tap the screen, if ArbiTrack isn’t already running and the marker is being detected, it will start markerless tracking, which in turn will trigger the arbiTrackStarted method and transfer over our image node.

You might be wondering why we implement the arbiTrackStarted method at all, since we could just run that same code when we tap the screen. The reason for this is that when ArbiTrack first starts, it doesn’t actually get data from the Gyroscope right away. Because of this, its world’s transform does not get updated for a couple of frames. If we tried to transfer the image node at this point, it would rotate and jump across the screen.

The arbiTrackStarted method is only called when ArbiTrack has started and has values, so we can be sure that when this method is called, it’s world has been updated it is safe to transfer our content across.

If you build and run your app now, you’ll find that you are able to detect a marker and, when you tap the screen, it will switch to markerless tracking. You can add some form of text or implement the touch control as a button press using an IBOutlet if you want more visual feedback when the tracking method changes.

So far so good, but right now we can only tap the screen once, and we can’t stop ArbiTrack and go back to marker tracking once we’ve made the switch. We’ll need to implement a way to toggle between tracking methods, transferring the image node as necessary.

Toggle tracking methods

This step is fairly simple. To go back to marker tracking, all we need to do is what we did to go to ArbiTrack, but in reverse. Plus, because the image tracker doesn’t have a world it needs to update, we can perform the transfer right in the touchesBegan method.

Since we already have an if statement checking whether ArbiTrack is running, all we need is an else:

ViewController.m
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
  // Get references to the ArbiTracker and Image Trackable.
  ARArbiTrackerManager *arbiTrack = [ARArbiTrackerManager getInstance];
  ARImageTrackable *imageTrackable = [[ARImageTrackerManager getInstance] findTrackableByName:@"Lego Marker"];
  
  // Only start ArbiTrack if it isn't currently running.
  if (!arbiTrack.isTracking)
  {
      // If the marker is not currently detected, exit the method and don't switch to arbitrack.
      if (!imageTrackable.isDetected)
      {
          return;
      }

      [arbiTrack start];
  }
  
  // If ArbiTrack is running
  else
  {
    // Find the Cow node in ArbiTrack's world.
    ARImageNode *cowNode = (ARImageNode *)[arbiTrack.world findChildWithName:@"Cow"];
    
    // Adjust the node's orientation to lie flat on the marker
    cowNode.orientation = [ARQuaternion quaternionWithIdentity];
    
    // Remove the Cow node from ArbiTrack's world and add it to the image trackable's world again.
    [arbiTrack.world removeChild:cowNode];
    [imageTrackable.world addChild:cowNode];
    
    // Stop ArbiTrack
    [arbiTrack stop];
  }

Now, if you build and run the app once more, you should see that you can tap the screen while tracking a marker to switch to ArbiTrack, and you can tap again to go back to marker tracking.