Android 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.

MainActivity.java
imageTrackable.setName("Lego Marker");
imageNode.setName("Cow");

Register the Activity 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 setup method:

MainActivity.java
// Initialise ArbiTrack
ARArbiTrack arbiTrack = ARArbiTrack.getInstance();
arbiTrack.initialise();
 
//Add the activity as an ArbiTrack delegate
arbiTrack.addListener(this);

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 setup method:

MainActivity.java
// Use the image trackable's world as the target node.
// This causes ArbiTrack to start tracking at the trackable's position.
arbiTrack.setTargetNode(imageTrackable.getWorld());

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 Activity. It looks like this:

MainActivity.java
// Delegate method called once ArbiTrack has started
@Override
public 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:

MainActivity.java
boolean firstRun = false;
     @Override
    public void arbiTrackStarted()
    {
        Log.d("K","=== ArbiTrack started ===");

       if(firstRun) {

       ARArbiTrack arArbiTrack = ARArbiTrack.getInstance();

       //Find a trackable registered with the tracker by its name
       ARImageTrackable legoTrackable = ARImageTracker.getInstance().findTrackable("Lego Marker");

       //Initialise an ARImageNode, find marker node,  and attach to ARArbitrack
       cowNode = new ARImageNode();
       cowNode = (ARImageNode) trackable.getWorld().findChildByName("Cow");    
       arArbiTrack.getWorld().addChild(cowNode);

       cowNode.remove();

       // Add the image node as a child of the trackable's world
       trackable.getWorld().addChild(imageNode);
  
       firstRun = false;

       }

     if(updateImage && trackable.getDetected()) {

        // Get arbi track instance
        ARArbiTrack arArbiTrack = ARArbiTrack.getInstance();

        //Get the markerless space to trackable space
        Matrix4f transform = arArbiTrack.getWorld().getFullTransform().invert().mult(trackable.getWorld().getFullTransform());

        //Get the rotation out of the full transform
        Quaternion orInArbiTrack = new Quaternion().fromRotationMatrix(transform.toRotationMatrix());

        //Get the position out of the full transform
        Vector3f posInArbiTrack = transform.mult(new Vector3f());

        // Add the image node as a child of arbi track
        arArbiTrack.getWorld().addChild(cowNode);

        // Change image nodes position to be relative to the marker nodes world
        cowNode.setPosition(posInArbiTrack);

        // Change image nodes orientation to be relative to the marker nodes world
        cowNode.setOrientation(orInArbiTrack);

     }
     updateImage = false;

  }

The ‘firstRun’ bool is necessary due to a bug in V1.5 of the framework that causes the method to be called earlier than it should. We are working to fix it, but in the meantime, use that as a workaround to prevent it being called during initialisation.

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 a different method as the one in the ArbiTrack Basics tutorial to respond to a button input. Instead of simply starting ArbiTrack in onSingleTapUp, we will call the following method:

MainActivity.java
public void toggleArbiTrack(View view)
    {
        Log.d("D","FLAG Button Pressed");

        // Get arbi track instance
        ARArbiTrack arArbiTrack = ARArbiTrack.getInstance();

        updateImage = true;

        if(!arArbiTrack.getIsTracking()) {

        // If the marker is not currently detected, exit the method and don't switch to arbitrack
             if(!trackable.getDetected())
             {
                 return;
             }

             imageNode.remove();

             arArbiTrack.start();
        }
        else {

             arArbiTrack.stop();

             imageNode.setPosition(new Vector3f());

             imageNode.setOrientation(new Quaternion());

             trackable.getWorld().addChild(imageNode);
         }
    }

Create a button to start toggleArbiTrack.

activity_main.xml
<Button
        android:id="@+id/button"
        ...
        android:onClick="toggleArbiTrack"
        ...

In this method, we need to know whether or not ArbiTrack is running when we tap the button, 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.

This means that when we tap the button, 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 button. 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, you’ll find that you are able to detect a marker and, when you tap the button, it will switch to markerless tracking. You can tap again to switch back to marker tracking. You can add some form of text if you want more visual feedback when the tracking method changes. Or better yet, change the one of the image nodes for some added fun.