iOS ArbiTrack Basics

This tutorial goes over the basics of using Kudan’s ArbiTrack feature.

This tutorial uses assets for the target node and image node. You can download these assets here.

In this asset bundle you should find:

  • Cow Target.png – This is the image we will use for our target node. This will move with the device’s Gyroscope and act as a preview for the ArbiTracker.
  • Cow Tracking.png – This is the image we will use for our image node, which will be displayed when ArbiTrack starts tracking.

Once you have downloaded the file, unzip it and add the assets to your Xcode project.

Initialise ArbiTrack

Before you can use Kudan’s ArbiTrack feature, there are two things you need to initialise. One is the ARArbiTrackerManager, which locks a node in place by tracking a set of feature points in the environment. The other is the ARGyroPlaceManager, which positions a node using your device’s Gyroscope. To do this, add the following code to your view controller:

ViewController.mViewController.swift
- (void)setupContent
{
  // Initialise ArbiTrack.
  ARArbiTrackerManager *arbiTrack = [ARArbiTrackerManager getInstance];
  [arbiTrack initialise];

  // Initialise gyro placement. 
  // Gyro placement positions content on a virtual floor plane.
  ARGyroPlaceManager *gyroPlaceManager = [ARGyroPlaceManager getInstance];
  [gyroPlaceManager initialise];
}
override func setupContent() 
{
	// Initialise ArbiTrack.
  let arbiTrack = ARArbiTrackerManager.getInstance()
  arbiTrack?.initialise()
        
  // Initialise gyro placement. 
  // Gyro placement positions content on a virtual floor plane.
  let gyroPlaceManager = ARGyroPlaceManager.getInstance()
  gyroPlaceManager?.initialise() 
}

This will initialise everything ArbiTrack needs to work, including the Gyroscope and KudanCV’s ArbiTracker.

Low-Feature Environments

ArbiTrack relies on having a large number of feature points in the environment to track correctly. If you are in an environment with a low number of features, this can cause tracking to become less consistent.

Setup the target node

To position our model in the world, we will need to use a target node. This node determines the starting point of tracking. Since the target node’s position changes depending on the orientation of the device, it is useful to have a graphical representation of where the target node is.

Because the target node is an ARNode, it is possible to use anything as a preview, including a simple preview image, or even the same model you intend to to track. In this tutorial we will be using an image, specifically the Kudan Cow, for our target.

To create a target node and add it to the ArbiTracker, add the following code at the end of the setupContent method:

ViewController.mViewController.swift
// Create a node to be used as the target.
ARImageNode *targetImageNode = [[ARImageNode alloc] initWithImage:[UIImage imageNamed:@"Cow Target"]];

// Add it to the Gyro Placement Manager's world so that it moves with the device's Gyroscope.
[gyroPlaceManager.world addChild:targetImageNode];

// Rotate and scale the node to ensure it is displayed correctly.
[targetImageNode rotateByDegrees:90 axisX:1 y:0 z:0];
[targetImageNode rotateByDegrees:180 axisX:0 y:1 z:0];

[targetImageNode scaleByUniform:0.3];
 
// Set the ArbiTracker's target node.
arbiTrack.targetNode = targetImageNode;
// Create a node to be used as the target.
let targetImageNode = ARImageNode(image: UIImage(named:"Cow Target"))
        
// Add it to the Gyro Placement Manager's world so that it moves with the device's Gyroscope.
gyroPlaceManager?.world.addChild(targetImageNode)
        
// Rotate and scale the node to ensure it is displayed correctly.
targetImageNode?.rotate(byDegrees: 90, axisX: 1, y: 0, z: 0)
targetImageNode?.rotate(byDegrees: 180, axisX: 0, y: 1, z: 0)

targetImageNode?.scale(byUniform: 0.3)
        
// Set the ArbiTracker's target node.
arbiTrack?.targetNode = targetImageNode

This will create an image node, add it to the Gyro Place Manager’s world and assign it to the ArbiTracker.

Setup content with ArbiTrack

We have a target node now, but we still need something to display when ArbiTrack starts. As with the target node, you can use any node with ArbiTrack to display whatever content you wish. For this tutorial, we’ll be sticking with image nodes. Add the following at the end of the setupContent method:

ViewController.mViewController.swift
// Create a node to be tracked.
ARImageNode *trackingImageNode = [[ARImageNode alloc] initWithImage:[UIImage imageNamed:@"Cow Tracking"]];

// Rotate the node to ensure it is displayed correctly.
[trackingImageNode rotateByDegrees:90 axisX:1 y:0 z:0];
[trackingImageNode rotateByDegrees:180 axisX:0 y:1 z:0];

// Add the node as a child of the ArbiTracker's world.
[arbiTrack.world addChild:trackingImageNode];
// Create a node to be tracked
let trackingImageNode = ARImageNode(image: UIImage(named:"Cow Tracking"))
        
// Rotate the node to ensure it is displayed correctly.
trackingImageNode?.rotate(byDegrees: 90, axisX: 1, y: 0, z: 0)
trackingImageNode?.rotate(byDegrees: 180, axisX: 0, y: 1, z: 0)
        
// Add the node as a child of the ArbiTracker's world.
arbiTrack?.world.addChild(trackingImageNode)

This will create an image node using the tracking image and add it to the ArbiTracker’s world.

Implement touch input and Start ArbiTrack.

Now we have a target node and an image node for content, and ArbiTrack and the Gyroscope are all set up and ready to go. But how do we start tracking? With the image tracker, it was automatic, because it would just look for markers, but the ArbiTracker has nothing like that to look for. This means we have to tell it when to start. Fortunately, that’s very easy to do.

There are many ways to allow input. We could add a button to the screen, for example. But that requires a lot of setup and messing around with IBOutlets. A much easier way is built right into the view controller. Add the following method to your view controller:

ViewController.mViewController.swift
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    ARArbiTrackerManager *arbiTrack = [ARArbiTrackerManager getInstance];
    [arbiTrack start];
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
{
		let arbiTrack = ARArbiTrackerManager.getInstance()
  	arbiTrack?.start()
}

This method allows your app to respond to any tap on the screen. All we’ve done is tell the ArbiTracker to start tracking whenever the screen is tapped.

Build and run the app and you should see the target node in the middle of the screen. Move the device around, and the target node will move with it. And if you tap the screen, ArbiTrack starts tracking. But hang on, the target node is still there. Additionally, if you tap the screen again, the images just move to a new position and start tracking from there. We should do something about that.

Hide the target node and toggle tracking

We’ll need to make a couple of changes to our touchesBegan method. First, let’s hide the target node. All we need to do is add one line of code:

ViewController.mViewController.swift
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    ARArbiTrackerManager *arbiTrack = [ARArbiTrackerManager getInstance];
    [arbiTrack start];
    arbiTrack.targetNode.visible = NO;
}
override func touchesBegan(_ touches: Set&amp;lt;UITouch&amp;gt;, with event: UIEvent?) 
{
		let arbiTrack = ARArbiTrackerManager.getInstance()
  	arbiTrack?.start()
    arbiTrack?.targetNode.visible = false
}

Now the target node will become invisible while tracking is active. If you build and run the app again, you should see that when you tap the screen, you only see the tracking image node.Next, let’s make it so we can stop tracking rather than restarting it on every tap. Let’s change the touchesBegan method again:

ViewController.mViewController.swift
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    ARArbiTrackerManager *arbiTrack = [ARArbiTrackerManager getInstance];
    
    if (arbiTrack.isTracking)
    {
        [arbiTrack stop];
        arbiTrack.targetNode.visible = YES;
    }
    
    else
    {
        [arbiTrack start];
        arbiTrack.targetNode.visible = NO;
    }
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
    let arbiTrack = ARArbiTrackerManager.getInstance()
        
    if (arbiTrack?.isTracking)!
    {
   	 	  arbiTrack?.stop()
        arbiTrack?.targetNode.visible = true
    }   
   
    else
    {
        arbiTrack?.start()
        arbiTrack?.targetNode.visible = false
    }
}

Here, we use arbiTrack.isTracking to check whether or not the tracker is running when we tap the screen. If it is, then we should stop tracking and make sure the target node is visible. This effectively puts us back into “target” mode. If tracking isn’t running, then start it and hide the target node, like we did before.

Build and run the app one more time, and you should see that when you tap the screen, the target node disappears and the tracking node takes its place. If you tap again, the tracking node disappears and the target node returns.