From 4261d6e6cc366884cd35daf66d9fa35c3dda6267 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen Date: Tue, 26 Sep 2023 12:27:06 +0700 Subject: tracker/tobii: add tobii input --- tracker-tobii/CMakeLists.txt | 17 ++-- tracker-tobii/ftnoir_tracker_tobii.cpp | 65 ------------- tracker-tobii/ftnoir_tracker_tobii.h | 50 ---------- tracker-tobii/ftnoir_tracker_tobii_controls.ui | 56 ------------ tracker-tobii/ftnoir_tracker_tobii_dialog.cpp | 19 ---- tracker-tobii/lang/nl_NL.ts | 12 ++- tracker-tobii/lang/ru_RU.ts | 12 ++- tracker-tobii/lang/stub.ts | 12 ++- tracker-tobii/lang/zh_CN.ts | 12 ++- tracker-tobii/thread.cpp | 109 ---------------------- tracker-tobii/thread.hpp | 35 ------- tracker-tobii/tobii.cpp | 121 +++++++++++++++++++++++++ tracker-tobii/tobii.h | 64 +++++++++++++ tracker-tobii/tobii.qrc | 5 + tracker-tobii/tobii.ui | 67 ++++++++++++++ tracker-tobii/tobii_dialog.cpp | 20 ++++ tracker-tobii/tobii_logo.png | Bin 0 -> 1768 bytes 17 files changed, 316 insertions(+), 360 deletions(-) delete mode 100644 tracker-tobii/ftnoir_tracker_tobii.cpp delete mode 100644 tracker-tobii/ftnoir_tracker_tobii.h delete mode 100644 tracker-tobii/ftnoir_tracker_tobii_controls.ui delete mode 100644 tracker-tobii/ftnoir_tracker_tobii_dialog.cpp delete mode 100644 tracker-tobii/thread.cpp delete mode 100644 tracker-tobii/thread.hpp create mode 100644 tracker-tobii/tobii.cpp create mode 100644 tracker-tobii/tobii.h create mode 100644 tracker-tobii/tobii.qrc create mode 100644 tracker-tobii/tobii.ui create mode 100644 tracker-tobii/tobii_dialog.cpp create mode 100644 tracker-tobii/tobii_logo.png (limited to 'tracker-tobii') diff --git a/tracker-tobii/CMakeLists.txt b/tracker-tobii/CMakeLists.txt index 8551c8fa..c70a874c 100644 --- a/tracker-tobii/CMakeLists.txt +++ b/tracker-tobii/CMakeLists.txt @@ -1,17 +1,14 @@ -# 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) +# https://developer.tobii.com/download-packages/tobii-xr-native-sdk/ +set(SDK_TOBII "" CACHE PATH "Tobii Stream Engine path") +if(WIN32 AND SDK_TOBII) otr_module(tracker-tobii) + target_include_directories(${self} SYSTEM PRIVATE "${SDK_TOBII}/include") + target_link_directories(${self} PRIVATE "${SDK_TOBII}/lib/tobii") 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}) + set(lib tobii_stream_engine.lib) - install(FILES ${dll} DESTINATION ${opentrack-hier-pfx}) target_link_libraries(${self} ${lib}) + install(FILES ${dll} DESTINATION ${opentrack-libexec}) endif() diff --git a/tracker-tobii/ftnoir_tracker_tobii.cpp b/tracker-tobii/ftnoir_tracker_tobii.cpp deleted file mode 100644 index 595cf410..00000000 --- a/tracker-tobii/ftnoir_tracker_tobii.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#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] = std::clamp(p.position_xyz[0] * 30.0 / 300.0, -30.0, 30.0); - data[1] = std::clamp(p.position_xyz[1] * 30.0 / 300.0, -30.0, 30.0); - data[2] = std::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] = std::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] = std::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] = std::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 deleted file mode 100644 index bd7a04a2..00000000 --- a/tracker-tobii/ftnoir_tracker_tobii.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once -#include "ui_ftnoir_tracker_tobii_controls.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 deleted file mode 100644 index 6cd00121..00000000 --- a/tracker-tobii/ftnoir_tracker_tobii_controls.ui +++ /dev/null @@ -1,56 +0,0 @@ - - - UITobiiControls - - - Qt::NonModal - - - - 0 - 0 - 498 - 303 - - - - Tobii settings - - - - ../gui/images/opentrack.png../gui/images/opentrack.png - - - - - 12 - - - 6 - - - 12 - - - 6 - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - buttonBox - - - - - startEngineClicked() - stopEngineClicked() - cameraSettingsClicked() - - diff --git a/tracker-tobii/ftnoir_tracker_tobii_dialog.cpp b/tracker-tobii/ftnoir_tracker_tobii_dialog.cpp deleted file mode 100644 index 5cd4bad0..00000000 --- a/tracker-tobii/ftnoir_tracker_tobii_dialog.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#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 index 45e4974f..538a31f1 100644 --- a/tracker-tobii/lang/nl_NL.ts +++ b/tracker-tobii/lang/nl_NL.ts @@ -2,16 +2,20 @@ - UITobiiControls + tobii_metadata - Tobii settings + Tobii Eye Tracker - tobiiDll + tobii_ui - Tobii input + Tobii Eye Tracker + + + + Please make sure the Tobii Experience application is running and tracking is active. diff --git a/tracker-tobii/lang/ru_RU.ts b/tracker-tobii/lang/ru_RU.ts index ee73b375..b86ba010 100644 --- a/tracker-tobii/lang/ru_RU.ts +++ b/tracker-tobii/lang/ru_RU.ts @@ -2,16 +2,20 @@ - UITobiiControls + tobii_metadata - Tobii settings + Tobii Eye Tracker - tobiiDll + tobii_ui - Tobii input + Tobii Eye Tracker + + + + Please make sure the Tobii Experience application is running and tracking is active. diff --git a/tracker-tobii/lang/stub.ts b/tracker-tobii/lang/stub.ts index cd8a2bdc..566900a3 100644 --- a/tracker-tobii/lang/stub.ts +++ b/tracker-tobii/lang/stub.ts @@ -2,16 +2,20 @@ - UITobiiControls + tobii_metadata - Tobii settings + Tobii Eye Tracker - tobiiDll + tobii_ui - Tobii input + Tobii Eye Tracker + + + + Please make sure the Tobii Experience application is running and tracking is active. diff --git a/tracker-tobii/lang/zh_CN.ts b/tracker-tobii/lang/zh_CN.ts index 9ea8f636..cbf60176 100644 --- a/tracker-tobii/lang/zh_CN.ts +++ b/tracker-tobii/lang/zh_CN.ts @@ -2,16 +2,20 @@ - UITobiiControls + tobii_metadata - Tobii settings + Tobii Eye Tracker - tobiiDll + tobii_ui - Tobii input + Tobii Eye Tracker + + + + Please make sure the Tobii Experience application is running and tracking is active. diff --git a/tracker-tobii/thread.cpp b/tracker-tobii/thread.cpp deleted file mode 100644 index bdd48125..00000000 --- a/tracker-tobii/thread.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#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 devices; - if (tobii_enumerate_local_device_urls(api, - [](char const* url, void* user_data) - { - auto list = (std::vector*) 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 deleted file mode 100644 index d311db87..00000000 --- a/tracker-tobii/thread.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include -#include - -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 exit_thread = false; -}; diff --git a/tracker-tobii/tobii.cpp b/tracker-tobii/tobii.cpp new file mode 100644 index 00000000..e25cf52a --- /dev/null +++ b/tracker-tobii/tobii.cpp @@ -0,0 +1,121 @@ +/* Copyright (c) 2023, Khoa Nguyen + + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted, + * provided that the above copyright notice and this permission + * notice appear in all copies. + */ + +#include "tobii.h" +#include "compat/math-imports.hpp" + +#include + +static constexpr double rad_to_deg = 180.0 * M_1_PI; +static constexpr double mm_to_cm = 0.1; + +static void url_receiver(char const* url, void* user_data) +{ + char* buffer = (char*)user_data; + if (*buffer != '\0') + return; // only keep first value + + if (strlen(url) < 256) + strcpy(buffer, url); +} + +static void head_pose_callback(tobii_head_pose_t const* head_pose, void* user_data) +{ + // Store the latest head pose data in the supplied storage + tobii_head_pose_t* head_pose_storage = (tobii_head_pose_t*)user_data; + *head_pose_storage = *head_pose; +} + +tobii_tracker::tobii_tracker() = default; + +tobii_tracker::~tobii_tracker() +{ + QMutexLocker lck(&mtx); + if (device) + { + tobii_head_pose_unsubscribe(device); + tobii_device_destroy(device); + } + if (api) + { + tobii_api_destroy(api); + } +} + +module_status tobii_tracker::start_tracker(QFrame*) +{ + QMutexLocker lck(&mtx); + tobii_error_t tobii_error = tobii_api_create(&api, nullptr, nullptr); + if (tobii_error != TOBII_ERROR_NO_ERROR) + { + return error("Failed to initialize the Tobii Stream Engine API."); + } + + char url[256] = { 0 }; + tobii_error = tobii_enumerate_local_device_urls(api, url_receiver, url); + if (tobii_error != TOBII_ERROR_NO_ERROR || url[0] == '\0') + { + tobii_api_destroy(api); + return error("No stream engine compatible device(s) found."); + } + + tobii_error = tobii_device_create(api, url, TOBII_FIELD_OF_USE_INTERACTIVE, &device); + if (tobii_error != TOBII_ERROR_NO_ERROR) + { + tobii_api_destroy(api); + return error(QString("Failed to connect to %1.").arg(url)); + } + + tobii_error = tobii_head_pose_subscribe(device, head_pose_callback, &latest_head_pose); + if (tobii_error != TOBII_ERROR_NO_ERROR) + { + tobii_device_destroy(device); + tobii_api_destroy(api); + return error("Failed to subscribe to head pose stream."); + } + + return status_ok(); +} + +void tobii_tracker::data(double* data) +{ + QMutexLocker lck(&mtx); + tobii_error_t tobii_error = tobii_device_process_callbacks(device); + if (tobii_error != TOBII_ERROR_NO_ERROR) + { + return; + } + + // Tobii coordinate system is different from OpenTrack's + // Tobii: +x is to the right, +y is up, +z is towards the user + // Rotation xyz is in radians, x is pitch, y is yaw, z is roll + + if (latest_head_pose.position_validity == TOBII_VALIDITY_VALID) + { + data[TX] = -latest_head_pose.position_xyz[0] * mm_to_cm; + data[TY] = latest_head_pose.position_xyz[1] * mm_to_cm; + data[TZ] = latest_head_pose.position_xyz[2] * mm_to_cm; + } + + if (latest_head_pose.rotation_validity_xyz[0] == TOBII_VALIDITY_VALID) + { + data[Pitch] = latest_head_pose.rotation_xyz[0] * rad_to_deg; + } + + if (latest_head_pose.rotation_validity_xyz[1] == TOBII_VALIDITY_VALID) + { + data[Yaw] = -latest_head_pose.rotation_xyz[1] * rad_to_deg; + } + + if (latest_head_pose.rotation_validity_xyz[2] == TOBII_VALIDITY_VALID) + { + data[Roll] = latest_head_pose.rotation_xyz[2] * rad_to_deg; + } +} + +OPENTRACK_DECLARE_TRACKER(tobii_tracker, tobii_dialog, tobii_metadata) diff --git a/tracker-tobii/tobii.h b/tracker-tobii/tobii.h new file mode 100644 index 00000000..dedda7af --- /dev/null +++ b/tracker-tobii/tobii.h @@ -0,0 +1,64 @@ +/* Copyright (c) 2023, Khoa Nguyen + + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted, + * provided that the above copyright notice and this permission + * notice appear in all copies. + */ + +#pragma once +#include "api/plugin-api.hpp" +#include "ui_tobii.h" + +#include +#include + +#include + +class tobii_tracker : public ITracker +{ +public: + tobii_tracker(); + ~tobii_tracker() override; + module_status start_tracker(QFrame*) override; + void data(double* data) override; + +private: + double last[6]{}; + + tobii_api_t* api = nullptr; + tobii_device_t* device = nullptr; + + tobii_head_pose_t latest_head_pose { + 0LL, + TOBII_VALIDITY_INVALID, + { 0.f, 0.f, 0.f }, + { TOBII_VALIDITY_INVALID, TOBII_VALIDITY_INVALID, TOBII_VALIDITY_INVALID }, + { 0.f, 0.f, 0.f }, + }; + + QMutex mtx; +}; + +class tobii_dialog : public ITrackerDialog +{ + Q_OBJECT + + Ui::tobii_ui ui; + +public: + tobii_dialog(); + void register_tracker(ITracker*) override {} + void unregister_tracker() override {} +private slots: + void doOK(); + void doCancel(); +}; + +class tobii_metadata : public Metadata +{ + Q_OBJECT + + QString name() override { return tr("Tobii Eye Tracker"); } + QIcon icon() override { return QIcon(":/images/tobii_logo.png"); } +}; diff --git a/tracker-tobii/tobii.qrc b/tracker-tobii/tobii.qrc new file mode 100644 index 00000000..3e785ef7 --- /dev/null +++ b/tracker-tobii/tobii.qrc @@ -0,0 +1,5 @@ + + + tobii_logo.png + + diff --git a/tracker-tobii/tobii.ui b/tracker-tobii/tobii.ui new file mode 100644 index 00000000..71b29c12 --- /dev/null +++ b/tracker-tobii/tobii.ui @@ -0,0 +1,67 @@ + + + tobii_ui + + + Qt::NonModal + + + + 0 + 0 + 278 + 58 + + + + Tobii Eye Tracker + + + + :/images/tobii_logo.png:/images/tobii_logo.png + + + Qt::LeftToRight + + + false + + + + + + + 0 + 0 + + + + Please make sure the Tobii Experience application is running and tracking is active. + + + + + + + + 0 + 0 + + + + QDialogButtonBox::Close + + + + + + + + + + + startEngineClicked() + stopEngineClicked() + cameraSettingsClicked() + + diff --git a/tracker-tobii/tobii_dialog.cpp b/tracker-tobii/tobii_dialog.cpp new file mode 100644 index 00000000..689cae38 --- /dev/null +++ b/tracker-tobii/tobii_dialog.cpp @@ -0,0 +1,20 @@ +#include "tobii.h" + +tobii_dialog::tobii_dialog() // NOLINT(cppcoreguidelines-pro-type-member-init) +{ + ui.setupUi(this); + + connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK())); + connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel())); +} + +void tobii_dialog::doOK() +{ + // s.b->save(); + close(); +} + +void tobii_dialog::doCancel() +{ + close(); +} diff --git a/tracker-tobii/tobii_logo.png b/tracker-tobii/tobii_logo.png new file mode 100644 index 00000000..3502b37b Binary files /dev/null and b/tracker-tobii/tobii_logo.png differ -- cgit v1.2.3