Drawing A Line Between Two Markers With Unity

Thorsten Bux
May 13, 2016

How to draw a (stretchy) line between two markers with Unity

In this tutorial you will learn how to draw a line between two markers with Unity and you will gain a deeper understanding of the ARTrackedObject script. Also you will learn how to put two markers and their coordinate system into relation to get the start and end point for the line.

Components for this tutorial

Setup the basics

You should have opened a new project with a new scene and already imported the ARUnity package. If you need support doing this please see Getting started with ARToolKit for Unity.

We now setup an AR scene as in the Getting started with ARToolKit for Unity tutorial with the difference that this scene will contain two ARMarkers and two ARTrackedObjects.

  1. Create a new GameObject called ARToolKitObject
  2. Drag the following scripts onto it
    • ARController
    • 2x ARMarker
      • The Type of both ARMarker is Square
  3. Change the Marker Tag of the first ARMarker script to hiro and select patt.hiro as Pattern file
  4. Change the Marker Tag of the seconed ARMarker script to kanji and select patt.kanji as Pattern file
  5. Create another GameObject called Scene root and drag AROrigin script to it
    • Set Layer to AR foreground
    • As child to Scene root create two GameObjects and rename them Marker scene and Marker scene 2
    • Drag ARTrackedObject script to Marker scene and Marker scene 2
      • In Marker scene set Marker tag to hiro
      • In Marker scene 2 set Marker tag to kanji
      • Verify that Got marker is true for both
    • As child to Scene root create a Camera object
      • Set culling mask to AR foreground
      • Drag ARCamera to this camera object

If you need help for this steps please consult Getting started with ARToolKit for Unity as the steps above are all described there in detail.

Now your scene should look like this:

ARToolKit Unity scene

The Line

Setup the line

Next step is to create a line object

  1. As child to Scene root create a new GameObject
  2. Name this object Line
  3. In the Inspector area select Add Component/Effects/Line Renderer
  4. Change Start Width and End Width to 0.01
    • As our markers have a width of 0.08 per default a line width of 0.01 makes for a nice line

Create the script

Now it is time to create a Unity script that sets the start and end point of the line to the centre of both tracked markers.

  1. In the Project area (lower left) select Assets/Scripts
  2. Right mouse click in the script area 
  3. Select Create/C# Script
  4. Name the script ConnectMarkersWithLine
  5. Double click the script to open the MonoDevelop-Unity editor

Implement the script

The basic things that we need to do in our script are:

  • Getting hold of the markers
  • Compute the relative transformation between these two markers
    • Therefor one marker needs to be the base marker

Getting hold of the markers

The first thing we need is to know is, if markers are visible in the field of view (FoV) of the camera.

Therefore ARTrackedObject class defines an event which is called every time a marker is found. The OnMarkerFound event. Accordingly if a marker is lost the OnMarkerLost  event is called. During tracking the OnMarkerTracked event is called regularly. 

You can have a look at the source of ARTrackedObject class in the MonoDevelop-Unity editor: Assembly-CSharp/Scripts/ARTrackedObject.cs

To use these events do the following:

  1. Go to our script ConnectMarkersWithLine.cs
  2. Implement the new methods: OnMarkerFound, OnMarkerLost  and OnMarkerTracked
using UnityEngine;
using System.Collections;

public class ConnectMarkersWithLine : MonoBehaviour {

    // Use this for initialization
    void Start () {
    }

    //These are our new methods
    void OnMarkerFound(ARMarker marker){}
    void OnMarkerLost(ARMarker marker){}
    void OnMarkerTracked(ARMarker marker){}

    // Update is called once per frame
    void Update () {   
    }
}

Now we can use OnMarkerFound to store references to the markers in member variables and determine our base marker. For defining the base marker AROrigin already provides a function, so lets use that.

As the LineRenderer is child of AROrigin and as we intend to place the ConnectMarkersWithLine script on this LineRenderer it is save to say that AROrigin is a parent of our script.

  1. Get hold on the AROrigin reference with the GetComponentInParent() function
  2. Call this function from inside the Start() function 
  3. Store the reference to AROrigin in a class member

AROrigin class member:

public class ConnectMarkersWithLine : MonoBehaviour {
    private AROrigin arOrigin;

Get AROrigin reference

//Use this for initialization
void Start(){
    arOrigin = this.gameObject.GetComponentInParent<AROrigin>();
}

Now lets get the base marker

  1. From within OnMarkerFound() call arOrigin.GetBaseMarker() and store this in a class member as well.
  2. To prevent to call GetBaseMarker() a second time once the target marker is found surround that call with a null check for baseMarker.

Which one of our two markers acts as the base marker might change because AROrigin sets the first visible marker as baseMarker. Because of that it is necessary to remove the reference to the base marker once that marker is lost.

ARMarkerclass member

public class ConnectMarkersWithLine : MonoBehaviour {
    private AROrigin arOrigin;
    private ARMarker baseMarker;

Get base marker

void OnMarkerFound(ARMarker marker){
    if(baseMarker==null){
        baseMarker = arOrigin.GetBaseMarker();
    }
}

Remove the reference if the base marker gets lost

  1. In the OnMarkerLost() function check if the lost marker is our baseMarker
  2. If it is the baseMarker update it with the new base marker from AROrigin (which might be null if there is no current base marker)

Handle base marker lost

void OnMarkerLost(ARMarker marker){
    if(baseMarker.Equals(marker)){
            baseMarker = arOrigin.GetBaseMarker();
    }
}

Compute the relative transformation

As we now have a base marker we can compute the relative transformation from that base marker to the target marker. 

  1. Compute the transformation from the baseMarker to target marker
    • baseMarker.TransformationMatrix.inverse * marker.TransformationMatrix
  2. And put that into world space via multiplying it with the world matrix which can be obtained from arOrigin
    • baseMarker.TransformationMatrix.inverse * marker.TransformationMatrix *  arOrigin.transform.localToWorldMatrix 
  3. Get the position from that matrix using the function PositionFromMatrix provided by the ARUtilityFunctions class

That is the target point of our line. The start point is simply the origin of the world as ARUnity sets the origin of the world according to the base marker.

Compute target position

Vector3 positionTarget = ARUtilityFunctions.PositionFromMatrix (arOrigin.transform.localToWorldMatrix * baseMarker.TransformationMatrix.inverse * marker.TransformationMatrix);

Start position

Vector3 positionStart = ARUtilityFunctions.PositionFromMatrix (arOrigin.transform.localToWorldMatrix);

Get the line object

The next thing the script needs to do is get a handle of the line object that we would like to manipulate. Therefore we need to add a Tag to the Line object.

  1. Got to Unity editor and select the Line in the Hierarchy area
  2. Then in the Inspector area select Tag/Add Tag…
  3. Create a new Tag named arLine
  4. In the code below I show you how you can get a handle on the Line and store it in a class member

Create tag Set tag

Define line object

public class ConnectMarkersWithLine : MonoBehaviour {
    private AROrigin arOrigin;
    private ARMarker baseMarker;   
    private LineRenderer line;

Get line handle

// Use this for initialization
void Start () {
    arOrigin = this.gameObject.GetComponentInParent<AROrigin>();
    line = GameObject.FindGameObjectWithTag("arLine").GetComponent<LineRenderer>();
}

Put the things together

Now all the things need to be put together. This is very straight forward:

  1. In OnMarkerTracked() verify that the currently tracked marker is not the baseMarker and that the baseMarker is not null
    • This is because only if there are two markers visible (one the baseMarker and one the targetMarker) a line can be drawn
  2. Enter the functions to compute positionStart and positionTarget after that verification
  3. Set the LineRenderer positions according to positionStart and positionTarget

Draw the line

void OnMarkerTracked(ARMarker marker){
    //Make sure that we have a baseMarker and another marker which is not the baseMarker
    if (baseMarker != null && !(baseMarker.Equals (marker))) {
        Vector3 positionStart = ARUtilityFunctions.PositionFromMatrix (arOrigin.transform.localToWorldMatrix);
        Vector3 positionTarget = ARUtilityFunctions.PositionFromMatrix (arOrigin.transform.localToWorldMatrix * baseMarker.TransformationMatrix.inverse * marker.TransformationMatrix);
        line.SetPosition (0, positionStart);
        line.SetPosition (1, positionTarget);
    }
}

Activate the script

  1. Go to Unity editor
  2. From the Project area/Assets/Scripts drag the ConnectMarkersWithLine script onto the line object 
  3. Make the Line the receiver for the marker events
    • Select Marker scene in the Hierarchy area
    • On the right in the Inspector area/AR Tracked Object (Script) section select the circle next to Event receiver
    • In the popup window double click the Line to mark it as receiver
    • Repeat this step for Marker scene 2

That’s it, now the line is drawn from one marker to the other.

Make it a stretchy line

You can tune up things a bit when you make the line smaller the further the markers are apart. That is very simple. Just put:

line.SetWidth (0.001f / positionTarget.magnitude, 0.001f / positionTarget.magnitude);

at the end of OnMarkerTracked() function.

The final result

You can download the script here: Unity draw line script

Feedback

I’d love to here about your experience with this tutorial. Please write your feedback in our ARToolKit forum.