diff options
Diffstat (limited to 'tracker-tobii')
-rw-r--r-- | tracker-tobii/CMakeLists.txt | 22 | ||||
-rw-r--r-- | tracker-tobii/lang/nl_NL.ts | 22 | ||||
-rw-r--r-- | tracker-tobii/lang/ru_RU.ts | 22 | ||||
-rw-r--r-- | tracker-tobii/lang/stub.ts | 22 | ||||
-rw-r--r-- | tracker-tobii/lang/zh_CN.ts | 22 | ||||
-rw-r--r-- | tracker-tobii/tobii.cpp | 121 | ||||
-rw-r--r-- | tracker-tobii/tobii.h | 61 | ||||
-rw-r--r-- | tracker-tobii/tobii.qrc | 5 | ||||
-rw-r--r-- | tracker-tobii/tobii.ui | 67 | ||||
-rw-r--r-- | tracker-tobii/tobii_dialog.cpp | 20 | ||||
-rw-r--r-- | tracker-tobii/tobii_logo.png | bin | 0 -> 1768 bytes |
11 files changed, 384 insertions, 0 deletions
diff --git a/tracker-tobii/CMakeLists.txt b/tracker-tobii/CMakeLists.txt new file mode 100644 index 00000000..8273d43e --- /dev/null +++ b/tracker-tobii/CMakeLists.txt @@ -0,0 +1,22 @@ +if(WIN32) + # https://www.nuget.org/packages/Tobii.StreamEngine.Native/ + set(SDK_TOBII "" CACHE PATH "Tobii Stream Engine path") +endif() +if(WIN32 AND SDK_TOBII) + otr_module(tracker-tobii) + + if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") + set(arch "x86") + else() + set(arch "x64") + endif() + + target_include_directories(${self} SYSTEM PRIVATE "${SDK_TOBII}/include") + target_link_directories(${self} PRIVATE "${SDK_TOBII}/lib/${arch}") + + set(dll "${SDK_TOBII}/lib/${arch}/tobii_stream_engine.dll") + set(lib tobii_stream_engine.lib) + + target_link_libraries(${self} ${lib}) + install(FILES ${dll} DESTINATION ${opentrack-libexec}) +endif() diff --git a/tracker-tobii/lang/nl_NL.ts b/tracker-tobii/lang/nl_NL.ts new file mode 100644 index 00000000..538a31f1 --- /dev/null +++ b/tracker-tobii/lang/nl_NL.ts @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="nl_NL"> +<context> + <name>tobii_metadata</name> + <message> + <source>Tobii Eye Tracker</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>tobii_ui</name> + <message> + <source>Tobii Eye Tracker</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Please make sure the Tobii Experience application is running and tracking is active.</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..b86ba010 --- /dev/null +++ b/tracker-tobii/lang/ru_RU.ts @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="ru_RU"> +<context> + <name>tobii_metadata</name> + <message> + <source>Tobii Eye Tracker</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>tobii_ui</name> + <message> + <source>Tobii Eye Tracker</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Please make sure the Tobii Experience application is running and tracking is active.</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..566900a3 --- /dev/null +++ b/tracker-tobii/lang/stub.ts @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +<context> + <name>tobii_metadata</name> + <message> + <source>Tobii Eye Tracker</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>tobii_ui</name> + <message> + <source>Tobii Eye Tracker</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Please make sure the Tobii Experience application is running and tracking is active.</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..908770e3 --- /dev/null +++ b/tracker-tobii/lang/zh_CN.ts @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="zh_CN"> +<context> + <name>tobii_metadata</name> + <message> + <source>Tobii Eye Tracker</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>tobii_ui</name> + <message> + <source>Tobii Eye Tracker</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Please make sure the Tobii Experience application is running and tracking is active.</source> + <translation type="unfinished">请确保 Tobii 应用程序正在运行,跟踪功能处于激活状态. </translation> + </message> +</context> +</TS> diff --git a/tracker-tobii/tobii.cpp b/tracker-tobii/tobii.cpp new file mode 100644 index 00000000..8cf59335 --- /dev/null +++ b/tracker-tobii/tobii.cpp @@ -0,0 +1,121 @@ +/* Copyright (c) 2023, Khoa Nguyen <khoanguyen@3forcom.com> + + * 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 <QMutexLocker> + +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, &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..414f9f64 --- /dev/null +++ b/tracker-tobii/tobii.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2023, Khoa Nguyen <khoanguyen@3forcom.com> + + * 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 <tobii/tobii.h> +#include <tobii/tobii_streams.h> + +#include <QMutex> + +class tobii_tracker : public ITracker +{ +public: + tobii_tracker(); + ~tobii_tracker() override; + module_status start_tracker(QFrame*) override; + void data(double* data) override; + +private: + tobii_api_t* api = nullptr; + tobii_device_t* device = nullptr; + + tobii_head_pose_t latest_head_pose{ + .timestamp_us = 0LL, + .position_validity = TOBII_VALIDITY_INVALID, + .position_xyz = { 0.f, 0.f, 0.f }, + .rotation_validity_xyz = { TOBII_VALIDITY_INVALID, TOBII_VALIDITY_INVALID, TOBII_VALIDITY_INVALID }, + .rotation_xyz = { 0.f, 0.f, 0.f }, + }; + + QMutex mtx; +}; + +class tobii_dialog : public ITrackerDialog +{ + Q_OBJECT + + Ui::tobii_ui ui; + +public: + tobii_dialog(); + +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 @@ +<RCC> + <qresource prefix="/images"> + <file>tobii_logo.png</file> + </qresource> +</RCC> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>tobii_ui</class> + <widget class="QWidget" name="tobii_ui"> + <property name="windowModality"> + <enum>Qt::NonModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>278</width> + <height>58</height> + </rect> + </property> + <property name="windowTitle"> + <string>Tobii Eye Tracker</string> + </property> + <property name="windowIcon"> + <iconset> + <normaloff>:/images/tobii_logo.png</normaloff>:/images/tobii_logo.png</iconset> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Please make sure the Tobii Experience application is running and tracking is active.</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="tobii.qrc"/> + </resources> + <connections/> + <slots> + <slot>startEngineClicked()</slot> + <slot>stopEngineClicked()</slot> + <slot>cameraSettingsClicked()</slot> + </slots> +</ui> 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 Binary files differnew file mode 100644 index 00000000..3502b37b --- /dev/null +++ b/tracker-tobii/tobii_logo.png |