Tobii Interaction Library SDK

[Back to the SDK start page]

C++ Sample Walkthrough

This section describes the typical workflow when using the C++ API of the Interaction Library SDK.

Let's assume the eye tracker is on the main screen and that the resolution is 2560x1440 pixels.

We want to tell the Interaction Library about 4 regions on screen (A, B, C and D in the picture below) for which we want to know when the user looks at them (or not). As our gaze enters and leaves them, the Interaction Library will emit "got focus" and "lost focus" events that we can react to in our client application.

Such a screen region is called an interactor in the Interaction Library API.

A screen with 4 "corner" interactors A, B, C and D:
2560
+-------+-------------------+-------+
| | | |
| A | 500 500 | B |
| | | |
+-------+ +-------+
| 500 500 |
| | 1440
| 500 500 |
+-------+ +-------+
| | | |
| C | 500 500 | D |
| | | |
+-------+------------------ +-------+

In the following, error checking is omitted for readability (most API functions return an IL::Result value, where IL::Result::Ok is the generic "success" value).

Make sure that the compiler and linker can find the Interaction Library C++ API headers and static or dynamic libraries, then include the main library header file:

#include <iostream>
#include <interaction_lib/InteractionLib.h>
#include <interaction_lib/misc/InteractionLibPtr.h>

Create an instance of the Interaction Library implementation with CreateInteractionLib() . Please note that the IL::InteractionLib pointer returned must be deleted with DestroyInteractionLib() . The Interaction Library SDK provides IL::UniqueInteractionLibPtr for convenience, which is just a typedef for a std::unique_ptr<> with a custom deleter that uses the aforementioned DestroyInteractionLib() (but you are of course under no obligation to use it).

int main()
{
// create the interaction library

Tell the Interaction Library our screen size with CoordinateTransformAddOrUpdateDisplayArea() and which origo our interactor coordinates are relative to with CoordinateTransformSetOriginOffset() . The latter is most useful if you want to use interactors with window local coordinates; simply update the offset as the window moves rather than all interactors.

constexpr float width = 2560.0f;
constexpr float height = 1440.0f;
constexpr float offset = 0.0f;
intlib->CoordinateTransformAddOrUpdateDisplayArea(width, height);
intlib->CoordinateTransformSetOriginOffset(offset, offset);

Tell the Interaction Library where our interactors are located with AddOrUpdateInteractor() . This must be done between matching calls to BeginInteractorUpdates() and CommitInteractorUpdates() , as changes will be handled in safe and optimized batches. An interactor is identified by a unique client supplied ID. Also, interactors can overlap each other and be stacked in z-order but we leave them all at 0 in this sample.

constexpr IL::InteractorId idA = 0;
constexpr IL::InteractorId idB = 1;
constexpr IL::InteractorId idC = 2;
constexpr IL::InteractorId idD = 3;
constexpr float size = 500.0f;
constexpr IL::Rectangle rectA = { 0, 0, size, size };
constexpr IL::Rectangle rectB = { width - size, 0, size, size };
constexpr IL::Rectangle rectC = { 0, height - size, size, size };
constexpr IL::Rectangle rectD = { width - size, height - size, size, size };
constexpr float z = 0.0f;
intlib->BeginInteractorUpdates();
intlib->AddOrUpdateInteractor(idA, rectA, z);
intlib->AddOrUpdateInteractor(idB, rectB, z);
intlib->AddOrUpdateInteractor(idC, rectC, z);
intlib->AddOrUpdateInteractor(idD, rectD, z);
intlib->CommitInteractorUpdates();

To get a way of breaking out of the "update loop" (described further down), we track if the same interactor gets focus 3 times in a row, and if so exit the program. We use a simple struct for that tracking.

struct Focus
{
size_t count = 0;
};
Focus focus;

Tell the Interaction Library we want to get interactor focus events with SubscribeGazeFocusEvents() . When our gaze enters and leaves the interactors, the lambda callback function provided will be called by the Interaction Library and the event information is printed to standard output.

intlib->SubscribeGazeFocusEvents([](IL::GazeFocusEvent evt, void* context)
{
Focus& focus = *static_cast<Focus*>(context);
std::cout
<< "Interactor: " << evt.id
<< ", focused: " << std::boolalpha << evt.hasFocus
<< ", timestamp: " << evt.timestamp_us << " us"
<< "\n";
if (evt.hasFocus)
{
focus.count = focus.id == evt.id ? focus.count + 1 : 1;
focus.id = evt.id;
}
}, &focus);

Tell the Interaction Library to continuously evaluate eye tracker gaze data and call any client event subscribers with WaitAndUpdate() . In the event that no device is connected, WaitAndUpdate() will attempt to find and connect to a tracker once per second. Since WaitAndUpdate() will often block the calling thread, you would normally put this loop on its own thread. WaitAndUpdate() is a convenience method that handles device connections, waiting for data and updating the Interaction Library to perform all calculation and trigger relevant callbacks all in one method. If you need granular control over updates and/or non-blocking calls use a suitable combination of the various other control methods available in the API.

std::cout << "Starting interaction library update loop.\n";
constexpr size_t max_focus_count = 3;
while (focus.count < max_focus_count)
{
intlib->WaitAndUpdate();
}
std::cout << "Interactor " << focus.id << " got focused " << focus.count << " times\n";
}

Finally:

  • Compile and run the sample code. Its entirety can be found here: C++ Sample Complete Code
  • Observe how gaze focus "enter" and "leave" event information gets printed to standard output.
  • Focus the same interactor 3 times in a row to exit the sample program.