summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBallista Milsim <ballista.milsim@gmail.com>2020-01-23 15:40:15 +0100
committerBallista Milsim <ballista.milsim@gmail.com>2020-01-23 15:40:15 +0100
commit77885b4d65f49fd220d2426c01cd336402b86c60 (patch)
treeafd0a202db23d7a1db8e6c0cb09f272759162d1a
parentfb994308266093382fffecb8a3fd2645ab811117 (diff)
WIP Tobii Eye tracker support.
-rw-r--r--tracker-tobii/CMakeLists.txt16
-rw-r--r--tracker-tobii/ftnoir_tracker_tobii.cpp60
-rw-r--r--tracker-tobii/ftnoir_tracker_tobii.h50
-rw-r--r--tracker-tobii/ftnoir_tracker_tobii_controls.ui56
-rw-r--r--tracker-tobii/ftnoir_tracker_tobii_dialog.cpp19
-rw-r--r--tracker-tobii/lang/nl_NL.ts18
-rw-r--r--tracker-tobii/lang/ru_RU.ts18
-rw-r--r--tracker-tobii/lang/stub.ts18
-rw-r--r--tracker-tobii/lang/zh_CN.ts18
-rw-r--r--tracker-tobii/thread.cpp118
-rw-r--r--tracker-tobii/thread.hpp37
11 files changed, 428 insertions, 0 deletions
diff --git a/tracker-tobii/CMakeLists.txt b/tracker-tobii/CMakeLists.txt
new file mode 100644
index 00000000..4593ae4e
--- /dev/null
+++ b/tracker-tobii/CMakeLists.txt
@@ -0,0 +1,16 @@
+# https://developer.tobii.com/download-packages/stream-engine-4-1-0-for-windows-x86
+# https://developer.tobii.com/download-packages/stream-engine-4-1-0-for-windows-x64
+if(SDK_TOBII)
+ otr_module(tracker-tobii)
+ target_include_directories(${self} SYSTEM PRIVATE "${SDK_TOBII}/include")
+
+ set(dll "${SDK_TOBII}/lib/tobii/tobii_stream_engine.dll")
+ set(lib "${SDK_TOBII}/lib/tobii/tobii_stream_engine.lib")
+
+ message(${self})
+ message(${dll})
+ message(${lib})
+
+ install(FILES ${dll} DESTINATION ${opentrack-hier-pfx})
+ target_link_libraries(${self} ${lib})
+endif()
diff --git a/tracker-tobii/ftnoir_tracker_tobii.cpp b/tracker-tobii/ftnoir_tracker_tobii.cpp
new file mode 100644
index 00000000..f0d66ae4
--- /dev/null
+++ b/tracker-tobii/ftnoir_tracker_tobii.cpp
@@ -0,0 +1,60 @@
+#include "ftnoir_tracker_tobii.h"
+#include "api/plugin-api.hpp"
+#include "compat/math.hpp"
+
+module_status tobii::start_tracker(QFrame*)
+{
+ t.start();
+ return status_ok();
+}
+
+void tobii::data(double *data)
+{
+ if (t.head_pose) {
+ tobii_head_pose_t p = *t.head_pose;
+ if (p.position_validity == TOBII_VALIDITY_VALID) {
+ if (center_pose.position_validity == TOBII_VALIDITY_VALID) {
+ p.position_xyz[0] = p.position_xyz[0] - center_pose.position_xyz[0];
+ p.position_xyz[1] = p.position_xyz[1] - center_pose.position_xyz[1];
+ p.position_xyz[2] = p.position_xyz[2] - center_pose.position_xyz[2];
+ }
+ data[0] = clamp(p.position_xyz[0] * 30.0 / 300.0, -30.0, 30.0);
+ data[1] = clamp(p.position_xyz[1] * 30.0 / 300.0, -30.0, 30.0);
+ data[2] = clamp(p.position_xyz[2] * 30.0 / 300.0, -30.0, 30.0);
+ }
+
+ double max_yaw = 90.0;
+ if (p.rotation_validity_xyz[1] == TOBII_VALIDITY_VALID) {
+ if (center_pose.rotation_validity_xyz[1] == TOBII_VALIDITY_VALID) {
+ p.rotation_xyz[1] = p.rotation_xyz[1] - center_pose.rotation_xyz[1];
+ }
+ data[3] = clamp(p.rotation_xyz[1] * 100.0 * max_yaw / 90.0, -max_yaw, max_yaw);
+ }
+
+ double max_pitch = 90.0;
+ if (p.rotation_validity_xyz[0] == TOBII_VALIDITY_VALID) {
+ if (center_pose.rotation_validity_xyz[0] == TOBII_VALIDITY_VALID) {
+ p.rotation_xyz[0] = p.rotation_xyz[0] - center_pose.rotation_xyz[0];
+ }
+ data[4] = clamp(p.rotation_xyz[0] * 100.0 * max_pitch / 90.0, -max_pitch, max_pitch);
+ }
+
+ double max_roll = 90.0;
+ if (p.rotation_validity_xyz[2] == TOBII_VALIDITY_VALID) {
+ if (center_pose.rotation_validity_xyz[2] == TOBII_VALIDITY_VALID) {
+ p.rotation_xyz[2] = p.rotation_xyz[2] - center_pose.rotation_xyz[2];
+ }
+ data[5] = clamp(p.rotation_xyz[2] * 100.0 * max_roll / 90.0, -max_roll, max_roll);
+ }
+ }
+}
+
+bool tobii::center ()
+{
+ if (t.head_pose) {
+ center_pose = *t.head_pose;
+ }
+ return false;
+}
+
+OPENTRACK_DECLARE_TRACKER(tobii, dialog_tobii, tobiiDll)
diff --git a/tracker-tobii/ftnoir_tracker_tobii.h b/tracker-tobii/ftnoir_tracker_tobii.h
new file mode 100644
index 00000000..bd7a04a2
--- /dev/null
+++ b/tracker-tobii/ftnoir_tracker_tobii.h
@@ -0,0 +1,50 @@
+#pragma once
+#include "ui_ftnoir_tracker_tobii_controls.h"
+#include <QComboBox>
+#include <QCheckBox>
+#include <QSpinBox>
+#include <QMessageBox>
+#include <QSettings>
+#include <QList>
+#include <QFrame>
+#include <QStringList>
+#include <cmath>
+#include "api/plugin-api.hpp"
+#include "options/options.hpp"
+
+#include "thread.hpp"
+
+class tobii : public ITracker
+{
+public:
+ ~tobii();
+ module_status start_tracker(QFrame*) override;
+ void data(double* data) override;
+ virtual bool center() override;
+private:
+ tobii_thread t;
+ tobii_head_pose_t center_pose;
+};
+
+class dialog_tobii: public ITrackerDialog
+{
+ Q_OBJECT
+public:
+ dialog_tobii();
+ ~dialog_tobii() = default;
+ void register_tracker(ITracker *) {}
+ void unregister_tracker() {}
+ Ui::UITobiiControls ui;
+ tobii* tracker;
+private slots:
+ void doOK();
+ void doCancel();
+};
+
+class tobiiDll : public Metadata
+{
+ Q_OBJECT
+
+ QString name() { return tr("Tobii input"); }
+ QIcon icon() { return QIcon(":/images/opentrack.png"); }
+};
diff --git a/tracker-tobii/ftnoir_tracker_tobii_controls.ui b/tracker-tobii/ftnoir_tracker_tobii_controls.ui
new file mode 100644
index 00000000..6cd00121
--- /dev/null
+++ b/tracker-tobii/ftnoir_tracker_tobii_controls.ui
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UITobiiControls</class>
+ <widget class="QWidget" name="UITobiiControls">
+ <property name="windowModality">
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>498</width>
+ <height>303</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Tobii settings</string>
+ </property>
+ <property name="windowIcon">
+ <iconset>
+ <normaloff>../gui/images/opentrack.png</normaloff>../gui/images/opentrack.png
+ </iconset>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>12</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>12</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+ <slots>
+ <slot>startEngineClicked()</slot>
+ <slot>stopEngineClicked()</slot>
+ <slot>cameraSettingsClicked()</slot>
+ </slots>
+</ui>
diff --git a/tracker-tobii/ftnoir_tracker_tobii_dialog.cpp b/tracker-tobii/ftnoir_tracker_tobii_dialog.cpp
new file mode 100644
index 00000000..5cd4bad0
--- /dev/null
+++ b/tracker-tobii/ftnoir_tracker_tobii_dialog.cpp
@@ -0,0 +1,19 @@
+#include "ftnoir_tracker_tobii.h"
+#include "api/plugin-api.hpp"
+
+dialog_tobii::dialog_tobii() : tracker(nullptr)
+{
+ ui.setupUi( this );
+
+ // Connect Qt signals to member-functions
+ connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
+ connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
+}
+
+void dialog_tobii::doOK() {
+ close();
+}
+
+void dialog_tobii::doCancel() {
+ close();
+}
diff --git a/tracker-tobii/lang/nl_NL.ts b/tracker-tobii/lang/nl_NL.ts
new file mode 100644
index 00000000..45e4974f
--- /dev/null
+++ b/tracker-tobii/lang/nl_NL.ts
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>UITobiiControls</name>
+ <message>
+ <source>Tobii settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>tobiiDll</name>
+ <message>
+ <source>Tobii input</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-tobii/lang/ru_RU.ts b/tracker-tobii/lang/ru_RU.ts
new file mode 100644
index 00000000..ee73b375
--- /dev/null
+++ b/tracker-tobii/lang/ru_RU.ts
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>UITobiiControls</name>
+ <message>
+ <source>Tobii settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>tobiiDll</name>
+ <message>
+ <source>Tobii input</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-tobii/lang/stub.ts b/tracker-tobii/lang/stub.ts
new file mode 100644
index 00000000..cd8a2bdc
--- /dev/null
+++ b/tracker-tobii/lang/stub.ts
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>UITobiiControls</name>
+ <message>
+ <source>Tobii settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>tobiiDll</name>
+ <message>
+ <source>Tobii input</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-tobii/lang/zh_CN.ts b/tracker-tobii/lang/zh_CN.ts
new file mode 100644
index 00000000..cd8a2bdc
--- /dev/null
+++ b/tracker-tobii/lang/zh_CN.ts
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>UITobiiControls</name>
+ <message>
+ <source>Tobii settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>tobiiDll</name>
+ <message>
+ <source>Tobii input</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-tobii/thread.cpp b/tracker-tobii/thread.cpp
new file mode 100644
index 00000000..f9fd2c01
--- /dev/null
+++ b/tracker-tobii/thread.cpp
@@ -0,0 +1,118 @@
+#include "thread.hpp"
+#include "compat/sleep.hpp"
+
+tobii_thread::tobii_thread()
+{
+ head_pose = new tobii_head_pose_t();
+ connect(this, &tobii_thread::tobii_ready_signal, this, &tobii_thread::tobii_ready_signal_impl, Qt::QueuedConnection);
+ connect(this, &tobii_thread::tobii_error_signal, this, &tobii_thread::tobii_error_signal_impl, Qt::QueuedConnection);
+}
+
+tobii_thread::~tobii_thread()
+{
+ if (device) tobii_device_destroy(device);
+ if (api) tobii_api_destroy(api);
+ exit_thread = true;
+ terminate();
+ wait();
+}
+
+void tobii_thread::run()
+{
+ /* See https://developer.tobii.com/consumer-eye-trackers/stream-engine/ */
+ if (tobii_api_create(&api, nullptr, nullptr) != TOBII_ERROR_NO_ERROR)
+ {
+ emit tobii_error_signal("Failed to initialize the Tobii Stream Engine API.");
+ }
+
+ std::vector<std::string> devices;
+ if (tobii_enumerate_local_device_urls(api,
+ [](char const* url, void* user_data)
+ {
+ auto list = (std::vector<std::string>*) user_data;
+ list->push_back(url);
+ }, &devices) != TOBII_ERROR_NO_ERROR)
+ {
+ emit tobii_error_signal("Failed to enumerate devices.");
+ }
+
+ if (devices.size() == 0)
+ {
+ tobii_api_destroy(api);
+ emit tobii_error_signal("No stream engine compatible device(s) found.");
+ }
+ std::string selected_device = devices[0];
+
+ unsigned int retry = 0;
+ tobii_error_t tobii_error = TOBII_ERROR_NO_ERROR;
+ do
+ {
+ tobii_error = tobii_device_create(api, selected_device.c_str(), TOBII_FIELD_OF_USE_INTERACTIVE, &device);
+ if ((tobii_error != TOBII_ERROR_CONNECTION_FAILED) && (tobii_error != TOBII_ERROR_FIRMWARE_UPGRADE_IN_PROGRESS)) break;
+ portable::sleep(interval);
+ ++retry;
+ } while (retry < retries);
+ if (tobii_error != TOBII_ERROR_NO_ERROR) {
+ tobii_api_destroy(api);
+ emit tobii_error_signal("Failed to connect to device.");
+ }
+
+ emit tobii_ready_signal();
+
+ tobii_error = TOBII_ERROR_NO_ERROR;
+ while (!exit_thread)
+ {
+ tobii_error = tobii_wait_for_callbacks(1, &device);
+ if (tobii_error == TOBII_ERROR_TIMED_OUT) continue;
+ else if (tobii_error != TOBII_ERROR_NO_ERROR)
+ {
+ emit tobii_error_signal("tobii_wait_for_callbacks failed.");
+ }
+
+ tobii_error = tobii_device_process_callbacks(device);
+ if (tobii_error == TOBII_ERROR_CONNECTION_FAILED)
+ {
+ unsigned int retry = 0;
+ auto tobii_error = TOBII_ERROR_NO_ERROR;
+ do
+ {
+ tobii_error = tobii_device_reconnect(device);
+ if ((tobii_error != TOBII_ERROR_CONNECTION_FAILED) && (tobii_error != TOBII_ERROR_FIRMWARE_UPGRADE_IN_PROGRESS)) break;
+ portable::sleep(interval);
+ ++retry;
+ } while (retry < retries);
+ if (tobii_error != TOBII_ERROR_NO_ERROR)
+ {
+ emit tobii_error_signal("Connection was lost and reconnection failed.");
+ }
+ continue;
+ }
+ else if (tobii_error != TOBII_ERROR_NO_ERROR)
+ {
+ emit tobii_error_signal("tobii_device_process_callbacks failed.");
+ }
+ }
+}
+
+void tobii_thread::tobii_error_signal_impl(QString error_message)
+{
+ //TODO: log? terminate?
+}
+
+void tobii_thread::tobii_ready_signal_impl()
+{
+ if (tobii_head_pose_subscribe(device, [](tobii_head_pose_t const* head_pose, void* user_data) {
+
+ if ((*head_pose).position_validity != TOBII_VALIDITY_VALID
+ || (*head_pose).rotation_validity_xyz[0] != TOBII_VALIDITY_VALID
+ || (*head_pose).rotation_validity_xyz[1] != TOBII_VALIDITY_VALID
+ || (*head_pose).rotation_validity_xyz[2] != TOBII_VALIDITY_VALID) return;
+
+ tobii_head_pose_t* tobii_head_pose_storage = (tobii_head_pose_t*)user_data;
+ *tobii_head_pose_storage = *head_pose;
+
+ }, head_pose) != TOBII_ERROR_NO_ERROR)
+ {
+ emit tobii_error_signal("Failed to subscribe to head pose stream.");
+ }
+}
diff --git a/tracker-tobii/thread.hpp b/tracker-tobii/thread.hpp
new file mode 100644
index 00000000..a2838272
--- /dev/null
+++ b/tracker-tobii/thread.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <QThread>
+#include <QCoreApplication>
+
+#include <tobii/tobii.h>
+#include <tobii/tobii_streams.h>
+#include <atomic>
+#include <vector>
+#include <string>
+
+class tobii_thread : public QThread
+{
+ Q_OBJECT
+ void run() override;
+
+signals:
+ void tobii_error_signal(QString error_message);
+ void tobii_ready_signal();
+
+public:
+ tobii_thread();
+ ~tobii_thread() override;
+
+ tobii_head_pose_t* head_pose;
+
+private:
+ tobii_api_t* api;
+ tobii_device_t* device;
+
+ const unsigned int retries = 300;
+ const unsigned int interval = 100;
+ std::atomic<bool> exit_thread = false;
+
+ void tobii_error_signal_impl(QString error_message);
+ void tobii_ready_signal_impl();
+};