diff options
-rw-r--r-- | tracker-tobii/CMakeLists.txt | 16 | ||||
-rw-r--r-- | tracker-tobii/ftnoir_tracker_tobii.cpp | 60 | ||||
-rw-r--r-- | tracker-tobii/ftnoir_tracker_tobii.h | 50 | ||||
-rw-r--r-- | tracker-tobii/ftnoir_tracker_tobii_controls.ui | 56 | ||||
-rw-r--r-- | tracker-tobii/ftnoir_tracker_tobii_dialog.cpp | 19 | ||||
-rw-r--r-- | tracker-tobii/lang/nl_NL.ts | 18 | ||||
-rw-r--r-- | tracker-tobii/lang/ru_RU.ts | 18 | ||||
-rw-r--r-- | tracker-tobii/lang/stub.ts | 18 | ||||
-rw-r--r-- | tracker-tobii/lang/zh_CN.ts | 18 | ||||
-rw-r--r-- | tracker-tobii/thread.cpp | 118 | ||||
-rw-r--r-- | tracker-tobii/thread.hpp | 37 |
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(); +}; |