summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2020-11-20 02:23:26 +0000
committerGitHub <noreply@github.com>2020-11-20 02:23:26 +0000
commit058942f40e17e091b91df5436d771d61203ccc73 (patch)
tree2562bb275b5ee9d5eed1cd383642d587aaf07ad2
parentf0e7870d66bbaf42ec0f1cd03dcc2da0dee6dd56 (diff)
parentcf89cd0ee392a73c7b92d0220b3963f1901908ae (diff)
Merge pull request #1037 from ballista-milsim/tracker-tobii
WIP Tobii Eye tracker support.
-rw-r--r--tracker-tobii/CMakeLists.txt17
-rw-r--r--tracker-tobii/ftnoir_tracker_tobii.cpp65
-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.cpp109
-rw-r--r--tracker-tobii/thread.hpp35
11 files changed, 423 insertions, 0 deletions
diff --git a/tracker-tobii/CMakeLists.txt b/tracker-tobii/CMakeLists.txt
new file mode 100644
index 00000000..8551c8fa
--- /dev/null
+++ b/tracker-tobii/CMakeLists.txt
@@ -0,0 +1,17 @@
+# 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
+set(SDK_TOBII "" CACHE PATH "Tobii SDK path")
+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..43cb1662
--- /dev/null
+++ b/tracker-tobii/ftnoir_tracker_tobii.cpp
@@ -0,0 +1,65 @@
+#include "ftnoir_tracker_tobii.h"
+#include "api/plugin-api.hpp"
+#include "compat/math.hpp"
+
+tobii::~tobii() = default;
+
+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];
+ }
+ else {
+ center_pose = p;
+ }
+ 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..bdd48125
--- /dev/null
+++ b/tracker-tobii/thread.cpp
@@ -0,0 +1,109 @@
+#include "thread.hpp"
+#include "compat/sleep.hpp"
+
+tobii_thread::~tobii_thread()
+{
+ exit_thread = true;
+ wait();
+
+ if (device) tobii_device_destroy(device);
+ if (api) tobii_api_destroy(api);
+
+ quit();
+}
+
+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)
+ {
+ error_last = "Failed to initialize the Tobii Stream Engine API.";
+ exit_thread = true;
+ }
+
+ 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)
+ {
+ error_last = "Failed to enumerate devices.";
+ exit_thread = true;
+ }
+
+ if (devices.size() == 0)
+ {
+ tobii_api_destroy(api);
+ error_last = "No stream engine compatible device(s) found.";
+ exit_thread = true;
+ }
+ 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);
+ error_last = "Failed to connect to device.";
+ exit_thread = true;
+ }
+
+ 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)
+ {
+ error_last = "Failed to subscribe to head pose stream.";
+ exit_thread = true;
+ }
+
+ 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)
+ {
+ error_last = "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)
+ {
+ error_last = "Connection was lost and reconnection failed.";
+ exit_thread = true;
+ }
+ continue;
+ }
+ else if (tobii_error != TOBII_ERROR_NO_ERROR)
+ {
+ error_last = "tobii_device_process_callbacks failed.";
+ }
+ }
+}
diff --git a/tracker-tobii/thread.hpp b/tracker-tobii/thread.hpp
new file mode 100644
index 00000000..d311db87
--- /dev/null
+++ b/tracker-tobii/thread.hpp
@@ -0,0 +1,35 @@
+#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;
+
+public:
+ tobii_thread()
+ {
+ head_pose = new tobii_head_pose_t();
+ }
+ ~tobii_thread() override;
+
+ tobii_head_pose_t* head_pose;
+
+private:
+ tobii_api_t* api;
+ tobii_device_t* device;
+
+ static constexpr unsigned int retries = 300;
+ static constexpr unsigned int interval = 100;
+
+ QString error_last = "";
+ std::atomic<bool> exit_thread = false;
+};