summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.clang-format47
-rw-r--r--.clang-tidy134
-rw-r--r--.editorconfig18
-rw-r--r--.gitattributes34
-rw-r--r--.github/CONTRIBUTING.md (renamed from CONTRIBUTING.md)0
-rw-r--r--.github/workflows/build-windows.bat3
-rw-r--r--.github/workflows/cmake.yml66
-rw-r--r--.github/workflows/linux-package.yml77
-rw-r--r--.gitignore18
-rw-r--r--.gitmodules3
-rw-r--r--3rdparty-notices/EIGEN-COPYING.txt379
-rw-r--r--3rdparty-notices/Kinect-V2-SDK-Eula.rtf525
-rw-r--r--3rdparty-notices/LIBUSB-COPYING.txt504
-rw-r--r--3rdparty-notices/ONNXRUNTIME.txt24
-rw-r--r--3rdparty-notices/ps3eyedriver/libusb0-AUTHORS.txt20
-rw-r--r--3rdparty-notices/ps3eyedriver/libusbK-AUTHORS.txt40
-rw-r--r--3rdparty-notices/ps3eyedriver/libusbK-LICENSE-bsd.txt33
-rw-r--r--3rdparty-notices/ps3eyedriver/libusbK-LICENSE-libusb.txt22
-rw-r--r--3rdparty-notices/ps3eyedriver/ps3eyedriver-LICENSE.txt45
-rw-r--r--3rdparty-notices/ps3eyedriver/ps3eyedriver-opentrack-LICENSE.txt17
-rw-r--r--AUTHORS.md1
-rw-r--r--CMakeLists.txt95
-rw-r--r--OPENTRACK-LICENSING.txt16
-rw-r--r--README.md43
-rw-r--r--api/CMakeLists.txt1
-rw-r--r--api/lang/de_DE.ts11
-rw-r--r--api/lang/nl_NL.ts7
-rw-r--r--api/lang/ru_RU.ts7
-rw-r--r--api/lang/stub.ts7
-rw-r--r--api/lang/zh_CN.ts9
-rw-r--r--api/plugin-api.cpp102
-rw-r--r--api/plugin-api.hpp141
-rw-r--r--api/plugin-support.hpp283
-rw-r--r--bin/NPClient.dllbin13824 -> 15872 bytes
-rw-r--r--bin/NPClient64.dllbin17408 -> 19968 bytes
-rw-r--r--bin/build-msvc.cmd52
-rw-r--r--bin/cleye.config6
-rw-r--r--bin/freetrackclient64.dllbin0 -> 16384 bytes
-rw-r--r--cmake/FindEigen3.cmake84
-rw-r--r--cmake/FindONNXRuntime.cmake104
-rw-r--r--cmake/GetGitRevisionDescription.cmake2
-rw-r--r--cmake/GetGitRevisionDescription.cmake.in2
-rw-r--r--cmake/apple.cmake15
-rw-r--r--cmake/linux.cmake6
-rw-r--r--cmake/mingw-w64.cmake70
-rw-r--r--cmake/mrproper.cmake (renamed from cmake/opentrack-clean-build-directory.cmake)8
-rw-r--r--cmake/msvc-clang.cmake131
-rw-r--r--cmake/msvc.cmake224
-rw-r--r--cmake/opentrack-boilerplate.cmake251
-rw-r--r--cmake/opentrack-check-build-directory.cmake7
-rw-r--r--cmake/opentrack-hier.cmake115
-rw-r--r--cmake/opentrack-i18n.cmake112
-rw-r--r--cmake/opentrack-install.cmake70
-rw-r--r--cmake/opentrack-load-user-settings.cmake24
-rw-r--r--cmake/opentrack-mrproper.cmake2
-rw-r--r--cmake/opentrack-opencv.cmake4
-rw-r--r--cmake/opentrack-org.cmake21
-rw-r--r--cmake/opentrack-pkg-config.cmake31
-rw-r--r--cmake/opentrack-platform.cmake204
-rw-r--r--cmake/opentrack-policy.cmake21
-rw-r--r--cmake/opentrack-qt.cmake81
-rw-r--r--cmake/opentrack-rift.cmake37
-rw-r--r--cmake/opentrack-variant.cmake61
-rw-r--r--cmake/opentrack-version.cmake20
-rw-r--r--cmake/opentrack-word-size.cmake11
-rw-r--r--cmake/translation-stub.ts4
-rw-r--r--compat/CMakeLists.txt9
-rw-r--r--compat/activation-context.cpp51
-rw-r--r--compat/activation-context.hpp26
-rw-r--r--compat/arch.hpp50
-rw-r--r--compat/base-path.cpp10
-rw-r--r--compat/base-path.hpp3
-rw-r--r--compat/camera-names.cpp117
-rw-r--r--compat/camera-names.hpp5
-rw-r--r--compat/check-visible.cpp105
-rw-r--r--compat/check-visible.hpp14
-rw-r--r--compat/copyable-mutex.cpp36
-rw-r--r--compat/copyable-mutex.hpp27
-rw-r--r--compat/correlation-calibrator.cpp44
-rw-r--r--compat/correlation-calibrator.hpp30
-rw-r--r--compat/enum-operators.hpp45
-rw-r--r--compat/euler.cpp61
-rw-r--r--compat/euler.hpp6
-rw-r--r--compat/hamilton-tools.cpp109
-rw-r--r--compat/hamilton-tools.h76
-rw-r--r--compat/lang/de_DE.ts4
-rw-r--r--compat/lang/zh_CN.ts2
-rw-r--r--compat/library-path.hpp2
-rw-r--r--compat/linkage-macros.hpp33
-rw-r--r--compat/macros.h57
-rw-r--r--compat/macros.hpp54
-rw-r--r--compat/math.hpp68
-rw-r--r--compat/meta.hpp124
-rw-r--r--compat/mutex.cpp33
-rw-r--r--compat/mutex.hpp24
-rw-r--r--compat/process-list.hpp65
-rw-r--r--compat/qhash.hpp26
-rw-r--r--compat/qt-dpi.hpp24
-rw-r--r--compat/qt-signal.cpp7
-rw-r--r--compat/qt-signal.hpp72
-rw-r--r--compat/run-in-thread.hpp108
-rw-r--r--compat/shm.cpp133
-rw-r--r--compat/shm.h17
-rw-r--r--compat/simple-mat.hpp250
-rw-r--r--compat/sleep.cpp23
-rw-r--r--compat/sleep.hpp23
-rw-r--r--compat/sysexits.hpp28
-rw-r--r--compat/thread-name.cpp87
-rw-r--r--compat/thread-name.hpp6
-rw-r--r--compat/time.hpp16
-rw-r--r--compat/timer.cpp88
-rw-r--r--compat/timer.hpp38
-rw-r--r--compat/tr.hpp14
-rw-r--r--compat/variance.hpp24
-rw-r--r--contrib-noinst/important-stuff/game-data.exebin0 -> 367104 bytes
-rw-r--r--contrib/freepie-udp/FreePIE_IMU_Android9.apkbin0 -> 1505724 bytes
-rw-r--r--contrib/freepie-udp/com.freepie.android.imu.apkbin167989 -> 0 bytes
-rw-r--r--contrib/freepie-udp/com.wishsalad.wishimu-debug.apkbin0 -> 1547581 bytes
-rw-r--r--contrib/freepie-udp/src-WishIMU.zipbin0 -> 132960 bytes
-rw-r--r--contrib/libusbK-dev-kit/libusbK-3.1.0.0-setup.exebin0 -> 7904435 bytes
-rw-r--r--contrib/libusbK-inf-wizard/libusbK-inf-wizard.exebin0 -> 9474560 bytes
-rw-r--r--contrib/libusbK-inf-wizard/license/AUTHORS-libusb0.txt20
-rw-r--r--contrib/libusbK-inf-wizard/license/AUTHORS-libusbK.txt40
-rw-r--r--contrib/libusbK-inf-wizard/license/LICENSE-bsd.txt33
-rw-r--r--contrib/libusbK-inf-wizard/license/LICENSE-gpl3.txt686
-rw-r--r--contrib/libusbK-inf-wizard/license/LICENSE-lgpl3.txt165
-rw-r--r--contrib/npclient/npclient.c283
-rw-r--r--coverity/build.sh2
-rw-r--r--csv/csv.cpp56
-rw-r--r--csv/lang/de_DE.ts4
-rw-r--r--csv/lang/zh_CN.ts2
-rw-r--r--cv/CMakeLists.txt14
-rw-r--r--cv/affine.cpp11
-rw-r--r--cv/affine.hpp11
-rw-r--r--cv/init.cpp24
-rw-r--r--cv/init.hpp2
-rw-r--r--cv/lang/de_DE.ts4
-rw-r--r--cv/lang/zh_CN.ts2
-rw-r--r--cv/numeric.hpp14
-rw-r--r--cv/ocv-check.cxx7
-rw-r--r--cv/translation-calibrator.cpp76
-rw-r--r--cv/translation-calibrator.hpp45
-rw-r--r--cv/video-widget.cpp150
-rw-r--r--cv/video-widget.hpp40
-rw-r--r--dinput/dinput.cpp124
-rw-r--r--dinput/dinput.hpp66
-rw-r--r--dinput/keybinding-worker.cpp320
-rw-r--r--dinput/keybinding-worker.hpp35
-rw-r--r--dinput/lang/zh_CN.ts2
-rw-r--r--dinput/win32-joystick.cpp215
-rw-r--r--dinput/win32-joystick.hpp74
-rw-r--r--docs/.gitignore1
-rw-r--r--docs/doxygen.conf2469
-rw-r--r--ext-falcon-bms-linear-acc/CMakeLists.txt1
-rw-r--r--ext-falcon-bms-linear-acc/FlightData.h577
-rw-r--r--ext-falcon-bms-linear-acc/bms-shm.cpp2
-rw-r--r--ext-falcon-bms-linear-acc/bms-shm.hpp2
-rw-r--r--ext-falcon-bms-linear-acc/falcon-bms-dialog.cpp9
-rw-r--r--ext-falcon-bms-linear-acc/falcon-bms-dialog.hpp10
-rw-r--r--ext-falcon-bms-linear-acc/falcon-bms-ext.cpp15
-rw-r--r--ext-falcon-bms-linear-acc/falcon-bms-ext.hpp11
-rw-r--r--ext-falcon-bms-linear-acc/falcon-bms-metadata.cpp11
-rw-r--r--ext-falcon-bms-linear-acc/falcon-bms-metadata.hpp9
-rw-r--r--ext-falcon-bms-linear-acc/module.cpp5
-rw-r--r--filter-accela-hamilton/CMakeLists.txt2
-rw-r--r--filter-accela-hamilton/accela_hamilton.cpp121
-rw-r--r--filter-accela-hamilton/accela_hamilton.h69
-rw-r--r--filter-accela-hamilton/accela_hamilton_dialog.cpp99
-rw-r--r--filter-accela-hamilton/accela_hamilton_filtercontrols.ui485
-rw-r--r--filter-accela-hamilton/accela_hamilton_settings.hpp63
-rw-r--r--filter-accela-hamilton/lang/de_DE.ts91
-rw-r--r--filter-accela-hamilton/lang/nl_NL.ts82
-rw-r--r--filter-accela-hamilton/lang/ru_RU.ts82
-rw-r--r--filter-accela-hamilton/lang/stub.ts82
-rw-r--r--filter-accela-hamilton/lang/zh_CN.ts82
-rw-r--r--filter-accela/accela-settings.hpp23
-rw-r--r--filter-accela/ftnoir_accela_filtercontrols.ui35
-rw-r--r--filter-accela/ftnoir_filter_accela.cpp114
-rw-r--r--filter-accela/ftnoir_filter_accela.h23
-rw-r--r--filter-accela/ftnoir_filter_accela_dialog.cpp16
-rw-r--r--filter-accela/lang/de_DE.ts57
-rw-r--r--filter-accela/lang/nl_NL.ts7
-rw-r--r--filter-accela/lang/ru_RU.ts7
-rw-r--r--filter-accela/lang/stub.ts7
-rw-r--r--filter-accela/lang/zh_CN.ts46
-rw-r--r--filter-ewma2/ftnoir_ewma_filtercontrols.ui6
-rw-r--r--filter-ewma2/ftnoir_filter_ewma2.cpp37
-rw-r--r--filter-ewma2/ftnoir_filter_ewma2.h18
-rw-r--r--filter-ewma2/lang/de_DE.ts72
-rw-r--r--filter-ewma2/lang/nl_NL.ts9
-rw-r--r--filter-ewma2/lang/ru_RU.ts9
-rw-r--r--filter-ewma2/lang/stub.ts9
-rw-r--r--filter-ewma2/lang/zh_CN.ts28
-rw-r--r--filter-hamilton/CMakeLists.txt1
-rw-r--r--filter-hamilton/ReadMe.txt13
-rw-r--r--filter-hamilton/ftnoir_filter_hamilton.cpp80
-rw-r--r--filter-hamilton/ftnoir_filter_hamilton.h74
-rw-r--r--filter-hamilton/ftnoir_filter_hamilton_dialog.cpp64
-rw-r--r--filter-hamilton/ftnoir_hamilton_filtercontrols.ui819
-rw-r--r--filter-hamilton/lang/de_DE.ts78
-rw-r--r--filter-hamilton/lang/nl_NL.ts78
-rw-r--r--filter-hamilton/lang/ru_RU.ts78
-rw-r--r--filter-hamilton/lang/stub.ts78
-rw-r--r--filter-hamilton/lang/zh_CN.ts78
-rw-r--r--filter-kalman/CMakeLists.txt5
-rw-r--r--filter-kalman/ftnoir_kalman_filtercontrols.ui170
-rw-r--r--filter-kalman/kalman.cpp297
-rw-r--r--filter-kalman/kalman.h179
-rw-r--r--filter-kalman/kalman_simulation.py76
-rw-r--r--filter-kalman/lang/nl_NL.ts31
-rw-r--r--filter-kalman/lang/ru_RU.ts31
-rw-r--r--filter-kalman/lang/stub.ts31
-rw-r--r--filter-nm/CMakeLists.txt1
-rw-r--r--filter-nm/ftnoir_filter_nm.cpp67
-rw-r--r--filter-nm/ftnoir_filter_nm.h81
-rw-r--r--filter-nm/ftnoir_filter_nm_dialog.cpp56
-rw-r--r--filter-nm/ftnoir_nm_filtercontrols.ui312
-rw-r--r--filter-nm/lang/de_DE.ts62
-rw-r--r--filter-nm/lang/nl_NL.ts (renamed from tracker-tobii-eyex/lang/nl_NL.ts)34
-rw-r--r--filter-nm/lang/ru_RU.ts (renamed from tracker-tobii-eyex/lang/ru_RU.ts)34
-rw-r--r--filter-nm/lang/stub.ts (renamed from tracker-tobii-eyex/lang/stub.ts)36
-rw-r--r--filter-nm/lang/zh_CN.ts62
-rw-r--r--freetrackclient/freetrackclient.c6
-rw-r--r--gui/CMakeLists.txt22
-rw-r--r--gui/correlation-calibrator.ui4
-rw-r--r--gui/images/english.pngbin283 -> 259 bytes
-rw-r--r--gui/images/settings16.pngbin711 -> 1136 bytes
-rw-r--r--gui/init.cpp301
-rw-r--r--gui/init.hpp5
-rw-r--r--gui/keyboard.cpp53
-rw-r--r--gui/keyboard.h16
-rw-r--r--gui/lang/de_DE.ts428
-rw-r--r--gui/lang/nl_NL.ts118
-rw-r--r--gui/lang/ru_RU.ts125
-rw-r--r--gui/lang/stub.ts124
-rw-r--r--gui/lang/zh_CN.ts269
-rw-r--r--gui/mapping-dialog.cpp86
-rw-r--r--gui/mapping-dialog.hpp5
-rw-r--r--gui/mapping-dialog.ui105
-rw-r--r--gui/options-dialog.cpp (renamed from gui/settings.cpp)253
-rw-r--r--gui/options-dialog.hpp57
-rw-r--r--gui/options-dialog.ui2364
-rw-r--r--gui/process_detector.cpp100
-rw-r--r--gui/process_detector.h28
-rw-r--r--gui/process_widget.ui13
-rw-r--r--gui/settings-dialog.ui2202
-rw-r--r--gui/settings.hpp31
-rw-r--r--installer/.gitignore1
-rw-r--r--installer/opentrack-installer.iss100
-rw-r--r--logic/CMakeLists.txt6
-rw-r--r--logic/extensions.cpp71
-rw-r--r--logic/extensions.hpp44
-rw-r--r--logic/lang/de_DE.ts64
-rw-r--r--logic/lang/nl_NL.ts51
-rw-r--r--logic/lang/ru_RU.ts51
-rw-r--r--logic/lang/stub.ts51
-rw-r--r--logic/lang/zh_CN.ts62
-rw-r--r--logic/main-settings.cpp70
-rw-r--r--logic/main-settings.hpp96
-rw-r--r--logic/mappings.hpp4
-rw-r--r--logic/pipeline.cpp519
-rw-r--r--logic/pipeline.hpp118
-rw-r--r--logic/runtime-libraries.cpp33
-rw-r--r--logic/runtime-libraries.hpp10
-rw-r--r--logic/shortcuts.cpp84
-rw-r--r--logic/shortcuts.h8
-rw-r--r--logic/state.cpp57
-rw-r--r--logic/state.hpp25
-rw-r--r--logic/tracklogger.cpp9
-rw-r--r--logic/tracklogger.hpp39
-rw-r--r--logic/win32-shortcuts.cpp81
-rw-r--r--logic/win32-shortcuts.h6
-rw-r--r--logic/work.cpp66
-rw-r--r--logic/work.hpp44
-rw-r--r--macosx/CMakeLists.txt6
-rw-r--r--macosx/Info.plist (renamed from macosx/opentrack.app/Contents/Info.plist)12
-rw-r--r--macosx/PkgInfo1
-rw-r--r--macosx/dmgbackground.pngbin0 -> 32425 bytes
-rwxr-xr-xmacosx/install-fail-tool10
-rwxr-xr-x[-rw-r--r--]macosx/make-app-bundle.sh102
-rw-r--r--macosx/opentrack.app/Contents/PkgInfo1
-rwxr-xr-xmacosx/opentrack.sh6
-rw-r--r--migration/20160906_00-mappings.cpp26
-rw-r--r--migration/20160906_01-axis-signs.cpp4
-rw-r--r--migration/20160906_02-modules.cpp4
-rw-r--r--migration/20170420_00-udp-naming.cpp2
-rw-r--r--migration/20171013_00-tracker-pt-threshold.cpp11
-rw-r--r--migration/20171020_00-max-pitch-output.cpp4
-rw-r--r--migration/20180102_00-process-detector-separator.cpp8
-rw-r--r--migration/20180118_00-reltrans.cpp4
-rw-r--r--migration/20180428_00-module-names.cpp92
-rw-r--r--migration/20220105_00-pt-grayscale.cpp38
-rw-r--r--migration/20220126_00-camera-name.cpp79
-rw-r--r--migration/CMakeLists.txt5
-rw-r--r--migration/lang/de_DE.ts4
-rw-r--r--migration/lang/zh_CN.ts2
-rw-r--r--migration/migration.cpp145
-rw-r--r--migration/migration.hpp67
-rw-r--r--opentrack/CMakeLists.txt25
-rw-r--r--opentrack/appnap_mac.mm47
-rw-r--r--opentrack/defs.hpp16
-rw-r--r--opentrack/lang/de_DE.ts193
-rw-r--r--opentrack/lang/nl_NL.ts (renamed from variant/default/lang/nl_NL.ts)16
-rw-r--r--opentrack/lang/ru_RU.ts (renamed from variant/default/lang/ru_RU.ts)4
-rw-r--r--opentrack/lang/stub.ts (renamed from variant/default/lang/stub.ts)16
-rw-r--r--opentrack/lang/zh_CN.ts (renamed from variant/default/lang/zh_CN.ts)4
-rw-r--r--opentrack/main-window.cpp1140
-rw-r--r--opentrack/main-window.hpp (renamed from variant/default/main-window.hpp)150
-rw-r--r--opentrack/main-window.ui1343
-rw-r--r--opentrack/main.cpp (renamed from variant/default/main.cpp)7
-rw-r--r--opentrack/new_config.ui (renamed from variant/default/new_config.ui)15
-rw-r--r--opentrack/new_file_dialog.cpp (renamed from variant/default/new_file_dialog.cpp)2
-rw-r--r--opentrack/new_file_dialog.h (renamed from variant/default/new_file_dialog.h)2
-rw-r--r--opentrack/opentrack.ico (renamed from variant/default/opentrack.ico)bin67134 -> 67134 bytes
-rw-r--r--opentrack/resources.rc (renamed from variant/default/resources.rc)0
-rw-r--r--options/base-value.cpp44
-rw-r--r--options/base-value.hpp118
-rw-r--r--options/bundle.cpp191
-rw-r--r--options/bundle.hpp102
-rw-r--r--options/connector.cpp125
-rw-r--r--options/connector.hpp50
-rw-r--r--options/defs.hpp7
-rw-r--r--options/globals.cpp195
-rw-r--r--options/globals.hpp74
-rw-r--r--options/group.cpp190
-rw-r--r--options/group.hpp102
-rw-r--r--options/lang/de_DE.ts4
-rw-r--r--options/lang/zh_CN.ts2
-rw-r--r--options/metatype.cpp36
-rw-r--r--options/metatype.hpp47
-rw-r--r--options/options.hpp3
-rw-r--r--options/scoped.cpp26
-rw-r--r--options/scoped.hpp13
-rw-r--r--options/slider.cpp56
-rw-r--r--options/slider.hpp51
-rw-r--r--options/tie.cpp109
-rw-r--r--options/tie.hpp56
-rw-r--r--options/value-traits.hpp127
-rw-r--r--options/value.cpp2
-rw-r--r--options/value.hpp237
-rw-r--r--pose-widget/CMakeLists.txt2
-rw-r--r--pose-widget/ReadMe.txt26
-rw-r--r--pose-widget/images/side1.pngbin540324 -> 35513 bytes
-rw-r--r--pose-widget/images/side6.pngbin543073 -> 35720 bytes
-rw-r--r--pose-widget/lang/de_DE.ts4
-rw-r--r--pose-widget/lang/zh_CN.ts2
-rw-r--r--pose-widget/pose-widget.cpp488
-rw-r--r--pose-widget/pose-widget.hpp95
-rw-r--r--presets/README.txt12
-rw-r--r--proto-flightgear/ftnoir_protocol_fg.cpp6
-rw-r--r--proto-flightgear/ftnoir_protocol_fg.h20
-rw-r--r--proto-flightgear/ftnoir_protocol_fg_dialog.cpp8
-rw-r--r--proto-flightgear/lang/de_DE.ts37
-rw-r--r--proto-flightgear/lang/nl_NL.ts18
-rw-r--r--proto-flightgear/lang/ru_RU.ts18
-rw-r--r--proto-flightgear/lang/stub.ts18
-rw-r--r--proto-flightgear/lang/zh_CN.ts20
-rw-r--r--proto-fsuipc/CMakeLists.txt5
-rw-r--r--proto-fsuipc/ftnoir_fsuipccontrols.ui104
-rw-r--r--proto-fsuipc/ftnoir_protocol_fsuipc.cpp76
-rw-r--r--proto-fsuipc/ftnoir_protocol_fsuipc.h39
-rw-r--r--proto-fsuipc/ftnoir_protocol_fsuipc_dialog.cpp13
-rw-r--r--proto-fsuipc/lang/nl_NL.ts22
-rw-r--r--proto-fsuipc/lang/ru_RU.ts22
-rw-r--r--proto-fsuipc/lang/stub.ts22
-rw-r--r--proto-fsuipc/lang/zh_CN.ts24
-rw-r--r--proto-ft/ftnoir_ftcontrols.ui209
-rw-r--r--proto-ft/ftnoir_protocol_ft.cpp202
-rw-r--r--proto-ft/ftnoir_protocol_ft.h50
-rw-r--r--proto-ft/ftnoir_protocol_ft_dialog.cpp29
-rw-r--r--proto-ft/lang/nl_NL.ts70
-rw-r--r--proto-ft/lang/ru_RU.ts70
-rw-r--r--proto-ft/lang/stub.ts70
-rw-r--r--proto-ft/lang/zh_CN.ts72
-rw-r--r--proto-iokit-foohid/CMakeLists.txt4
-rw-r--r--proto-iokit-foohid/foohidjoystick.cpp10
-rw-r--r--proto-iokit-foohid/foohidjoystick.h2
-rw-r--r--proto-iokit-foohid/iokitprotocol.cpp20
-rw-r--r--proto-iokit-foohid/iokitprotocol.h7
-rw-r--r--proto-iokit-foohid/lang/zh_CN.ts (renamed from ext-falcon-bms-linear-acc/lang/zh_CN.ts)2
-rw-r--r--proto-libevdev/ftnoir_libevdev_controls.ui2
-rw-r--r--proto-libevdev/ftnoir_protocol_libevdev.cpp87
-rw-r--r--proto-libevdev/ftnoir_protocol_libevdev.h29
-rw-r--r--proto-libevdev/lang/de_DE.ts37
-rw-r--r--proto-libevdev/lang/nl_NL.ts26
-rw-r--r--proto-libevdev/lang/ru_RU.ts26
-rw-r--r--proto-libevdev/lang/stub.ts26
-rw-r--r--proto-libevdev/lang/zh_CN.ts (renamed from filter-kalman/lang/zh_CN.ts)22
-rw-r--r--proto-mouse/ftnoir_mousecontrols.ui201
-rw-r--r--proto-mouse/ftnoir_protocol_mouse.cpp85
-rw-r--r--proto-mouse/ftnoir_protocol_mouse.h45
-rw-r--r--proto-mouse/ftnoir_protocol_mouse_dialog.cpp19
-rw-r--r--proto-mouse/lang/nl_NL.ts44
-rw-r--r--proto-mouse/lang/ru_RU.ts44
-rw-r--r--proto-mouse/lang/stub.ts44
-rw-r--r--proto-mouse/lang/zh_CN.ts60
-rw-r--r--proto-mouse/mouse-settings.hpp24
-rw-r--r--proto-osc/CMakeLists.txt15
-rw-r--r--proto-osc/dialog.cpp38
-rw-r--r--proto-osc/dialog.hpp29
-rw-r--r--proto-osc/dialog.ui131
-rw-r--r--proto-osc/images/osc-icon.pngbin0 -> 868 bytes
-rw-r--r--proto-osc/lang/nl_NL.ts (renamed from tracker-rift-025/lang/nl_NL.ts)31
-rw-r--r--proto-osc/lang/ru_RU.ts (renamed from tracker-rift-025/lang/ru_RU.ts)31
-rw-r--r--proto-osc/lang/stub.ts (renamed from tracker-rift-025/lang/stub.ts)31
-rw-r--r--proto-osc/lang/zh_CN.ts (renamed from tracker-rift-025/lang/zh_CN.ts)33
-rw-r--r--proto-osc/metadata.cpp8
-rw-r--r--proto-osc/metadata.hpp10
-rw-r--r--proto-osc/osc-res.qrc5
-rw-r--r--proto-osc/proto.cpp54
-rw-r--r--proto-osc/proto.hpp29
-rw-r--r--proto-osc/settings.hpp14
-rw-r--r--proto-simconnect/CMakeLists.txt2
-rw-r--r--proto-simconnect/ftnoir-protocol-sc.rc8
-rw-r--r--proto-simconnect/ftnoir_protocol_sc.cpp240
-rw-r--r--proto-simconnect/ftnoir_protocol_sc.h84
-rw-r--r--proto-simconnect/ftnoir_protocol_sc_dialog.cpp10
-rw-r--r--proto-simconnect/ftnoir_sccontrols.ui10
-rw-r--r--proto-simconnect/lang/nl_NL.ts29
-rw-r--r--proto-simconnect/lang/ru_RU.ts29
-rw-r--r--proto-simconnect/lang/stub.ts29
-rw-r--r--proto-simconnect/lang/zh_CN.ts31
-rw-r--r--proto-simconnect/manifest-template.in16
-rw-r--r--proto-udp/ftnoir_protocol_ftn.cpp15
-rw-r--r--proto-udp/ftnoir_protocol_ftn.h23
-rw-r--r--proto-udp/lang/de_DE.ts37
-rw-r--r--proto-udp/lang/nl_NL.ts11
-rw-r--r--proto-udp/lang/ru_RU.ts11
-rw-r--r--proto-udp/lang/stub.ts11
-rw-r--r--proto-udp/lang/zh_CN.ts13
-rw-r--r--proto-vjoystick/CMakeLists.txt4
-rw-r--r--proto-vjoystick/lang/nl_NL.ts59
-rw-r--r--proto-vjoystick/lang/ru_RU.ts59
-rw-r--r--proto-vjoystick/lang/stub.ts59
-rw-r--r--proto-vjoystick/lang/zh_CN.ts61
-rw-r--r--proto-vjoystick/vjoystick.cpp149
-rw-r--r--proto-vjoystick/vjoystick.h54
-rw-r--r--proto-vjoystick/vjoystick.ui21
-rw-r--r--proto-vjoystick/vjoystick_dialog.cpp3
-rw-r--r--proto-wine/CMakeLists.txt30
-rw-r--r--proto-wine/ftnoir_protocol_wine.cpp194
-rw-r--r--proto-wine/ftnoir_protocol_wine.h80
-rw-r--r--proto-wine/ftnoir_protocol_wine_dialog.cpp225
-rw-r--r--proto-wine/ftnoir_winecontrols.ui505
-rw-r--r--proto-wine/lang/de_DE.ts140
-rw-r--r--proto-wine/lang/nl_NL.ts129
-rw-r--r--proto-wine/lang/ru_RU.ts129
-rw-r--r--proto-wine/lang/stub.ts129
-rw-r--r--proto-wine/lang/zh_CN.ts140
-rw-r--r--proto-wine/opentrack-wrapper-wine-main.cxx14
-rw-r--r--proto-wine/opentrack-wrapper-wine-windows.cxx37
-rw-r--r--proto-wine/proton.cpp97
-rw-r--r--proto-wine/proton.h7
-rw-r--r--proto-wine/wine-shm.h12
-rw-r--r--qxt-mini/CMakeLists.txt16
-rw-r--r--qxt-mini/lang/de_DE.ts4
-rw-r--r--qxt-mini/lang/zh_CN.ts4
-rw-r--r--qxt-mini/powerset.hpp5
-rw-r--r--qxt-mini/qplatformnativeinterface.h99
-rw-r--r--qxt-mini/qxtglobalshortcut.cpp38
-rw-r--r--qxt-mini/qxtglobalshortcut.h8
-rw-r--r--qxt-mini/qxtglobalshortcut_mac.cpp17
-rw-r--r--qxt-mini/qxtglobalshortcut_p.h5
-rw-r--r--qxt-mini/qxtglobalshortcut_x11.cpp67
-rw-r--r--qxt-mini/x11-keymap.cpp17
-rw-r--r--qxt-mini/x11-keymap.hpp22
-rw-r--r--redist/x64/msvcp110.dllbin0 -> 661456 bytes
-rw-r--r--redist/x64/msvcr110.dllbin0 -> 849360 bytes
-rw-r--r--redist/x86/msvcp110.dllbin0 -> 535008 bytes
-rw-r--r--redist/x86/msvcr110.dllbin0 -> 875472 bytes
-rw-r--r--sdk-paths-runner@GNU-Linux.cmake3
-rw-r--r--sdk-paths-runneradmin@MSVC-windows.cmake33
-rw-r--r--sdk-paths-sthalik@Clang-Linux.cmake55
-rw-r--r--sdk-paths-sthalik@Clang-windows.cmake70
-rw-r--r--sdk-paths-sthalik@GNU-windows.cmake40
-rw-r--r--sdk-paths-sthalik@MSVC-windows.cmake75
-rw-r--r--settings/facetracknoir supported games.csv94
-rw-r--r--spline/CMakeLists.txt1
-rw-r--r--spline/axis-opts.cpp15
-rw-r--r--spline/axis-opts.hpp29
-rw-r--r--spline/broken/qfunctionconfiguratorplugin.h2
-rw-r--r--spline/lang/de_DE.ts4
-rw-r--r--spline/lang/zh_CN.ts2
-rw-r--r--spline/spline-widget.cpp493
-rw-r--r--spline/spline-widget.hpp76
-rw-r--r--spline/spline.cpp575
-rw-r--r--spline/spline.hpp124
-rw-r--r--tracker-aruco/CMakeLists.txt45
-rw-r--r--tracker-aruco/aruco-trackercontrols.ui129
-rw-r--r--tracker-aruco/compile-test/abi.cpp7
-rw-r--r--tracker-aruco/ftnoir_tracker_aruco.cpp231
-rw-r--r--tracker-aruco/ftnoir_tracker_aruco.h112
-rw-r--r--tracker-aruco/include/aruco.h134
-rw-r--r--tracker-aruco/include/arucofidmarkers.h119
-rw-r--r--tracker-aruco/include/board.h168
-rw-r--r--tracker-aruco/include/boarddetector.h138
-rw-r--r--tracker-aruco/include/cameraparameters.h138
-rw-r--r--tracker-aruco/include/cvdrawingutils.h52
-rw-r--r--tracker-aruco/include/exports.h46
-rw-r--r--tracker-aruco/include/marker.h143
-rw-r--r--tracker-aruco/include/markerdetector.h360
-rw-r--r--tracker-aruco/lang/nl_NL.ts47
-rw-r--r--tracker-aruco/lang/ru_RU.ts45
-rw-r--r--tracker-aruco/lang/stub.ts45
-rw-r--r--tracker-aruco/lang/zh_CN.ts67
-rw-r--r--tracker-easy/CMakeLists.txt25
-rw-r--r--tracker-easy/Resources/cap_front.pngbin0 -> 1164 bytes
-rw-r--r--tracker-easy/Resources/cap_side.pngbin0 -> 1733 bytes
-rw-r--r--tracker-easy/Resources/clip_front.pngbin0 -> 571 bytes
-rw-r--r--tracker-easy/Resources/clip_side.pngbin0 -> 2677 bytes
-rw-r--r--tracker-easy/Resources/easy-tracker-logo.pngbin0 -> 18357 bytes
-rw-r--r--tracker-easy/doc/index.htm262
-rw-r--r--tracker-easy/doc/logo.pngbin0 -> 10386 bytes
-rw-r--r--tracker-easy/doc/ptrack.icobin0 -> 4286 bytes
-rw-r--r--tracker-easy/doc/settings1.pngbin0 -> 25013 bytes
-rw-r--r--tracker-easy/doc/settings2.pngbin0 -> 26841 bytes
-rw-r--r--tracker-easy/doc/settings3.pngbin0 -> 29547 bytes
-rw-r--r--tracker-easy/doc/style.css131
-rw-r--r--tracker-easy/export.hpp11
-rw-r--r--tracker-easy/kalman-filter-pose.cpp118
-rw-r--r--tracker-easy/kalman-filter-pose.h32
-rw-r--r--tracker-easy/lang/de_DE.ts250
-rw-r--r--tracker-easy/lang/nl_NL.ts250
-rw-r--r--tracker-easy/lang/ru_RU.ts250
-rw-r--r--tracker-easy/lang/stub.ts250
-rw-r--r--tracker-easy/lang/zh_CN.ts250
-rw-r--r--tracker-easy/module.cpp15
-rw-r--r--tracker-easy/module.hpp20
-rw-r--r--tracker-easy/ocv-check.cxx18
-rw-r--r--tracker-easy/point-extractor.cpp168
-rw-r--r--tracker-easy/point-extractor.h46
-rw-r--r--tracker-easy/preview.cpp128
-rw-r--r--tracker-easy/preview.h46
-rw-r--r--tracker-easy/settings.h65
-rw-r--r--tracker-easy/tracker-easy-dialog.cpp217
-rw-r--r--tracker-easy/tracker-easy-dialog.h48
-rw-r--r--tracker-easy/tracker-easy-settings.ui1118
-rw-r--r--tracker-easy/tracker-easy.cpp912
-rw-r--r--tracker-easy/tracker-easy.h168
-rw-r--r--tracker-easy/tracker_easy.qrc9
-rw-r--r--tracker-eyeware-beam/CMakeLists.txt27
-rw-r--r--tracker-eyeware-beam/eyeware_beam.cpp116
-rw-r--r--tracker-eyeware-beam/eyeware_beam.h70
-rw-r--r--tracker-eyeware-beam/eyeware_beam.qrc5
-rw-r--r--tracker-eyeware-beam/eyeware_beam.ui90
-rw-r--r--tracker-eyeware-beam/eyeware_beam_logo.pngbin0 -> 2032 bytes
-rw-r--r--tracker-eyeware-beam/lang/nl_NL.ts20
-rw-r--r--tracker-eyeware-beam/lang/ru_RU.ts20
-rw-r--r--tracker-eyeware-beam/lang/stub.ts20
-rw-r--r--tracker-eyeware-beam/lang/zh_CN.ts20
-rw-r--r--tracker-freepie-udp/ftnoir_tracker_freepie-udp.cpp16
-rw-r--r--tracker-freepie-udp/ftnoir_tracker_freepie-udp.h5
-rw-r--r--tracker-freepie-udp/lang/de_DE.ts86
-rw-r--r--tracker-freepie-udp/lang/nl_NL.ts7
-rw-r--r--tracker-freepie-udp/lang/ru_RU.ts7
-rw-r--r--tracker-freepie-udp/lang/stub.ts7
-rw-r--r--tracker-freepie-udp/lang/zh_CN.ts9
-rw-r--r--tracker-fusion/fusion.cpp33
-rw-r--r--tracker-fusion/fusion.h16
-rw-r--r--tracker-fusion/lang/de_DE.ts56
-rw-r--r--tracker-fusion/lang/nl_NL.ts7
-rw-r--r--tracker-fusion/lang/ru_RU.ts7
-rw-r--r--tracker-fusion/lang/stub.ts7
-rw-r--r--tracker-fusion/lang/zh_CN.ts9
-rw-r--r--tracker-hatire/CMakeLists.txt7
-rw-r--r--tracker-hatire/ftnoir_hatcontrols.ui118
-rw-r--r--tracker-hatire/ftnoir_tracker_hat.cpp45
-rw-r--r--tracker-hatire/ftnoir_tracker_hat.h23
-rw-r--r--tracker-hatire/ftnoir_tracker_hat_dialog.cpp14
-rw-r--r--tracker-hatire/ftnoir_tracker_hat_dialog.h4
-rw-r--r--tracker-hatire/ftnoir_tracker_hat_settings.h4
-rw-r--r--tracker-hatire/lang/nl_NL.ts19
-rw-r--r--tracker-hatire/lang/ru_RU.ts19
-rw-r--r--tracker-hatire/lang/stub.ts19
-rw-r--r--tracker-hatire/lang/zh_CN.ts21
-rw-r--r--tracker-hatire/thread.cpp50
-rw-r--r--tracker-hatire/thread.hpp9
-rw-r--r--tracker-hydra/CMakeLists.txt38
-rw-r--r--tracker-hydra/ftnoir_tracker_hydra.cpp5
-rw-r--r--tracker-hydra/ftnoir_tracker_hydra.h16
-rw-r--r--tracker-hydra/lang/zh_CN.ts2
-rw-r--r--tracker-joystick/ftnoir_tracker_joystick.cpp12
-rw-r--r--tracker-joystick/ftnoir_tracker_joystick.h9
-rw-r--r--tracker-joystick/ftnoir_tracker_joystick_dialog.cpp14
-rw-r--r--tracker-joystick/lang/nl_NL.ts7
-rw-r--r--tracker-joystick/lang/ru_RU.ts7
-rw-r--r--tracker-joystick/lang/stub.ts7
-rw-r--r--tracker-joystick/lang/zh_CN.ts9
-rw-r--r--tracker-kinect-face/CMakeLists.txt55
-rw-r--r--tracker-kinect-face/camera_kinect_ir.cpp301
-rw-r--r--tracker-kinect-face/camera_kinect_ir.h82
-rw-r--r--tracker-kinect-face/images/kinect.pngbin0 -> 217 bytes
-rw-r--r--tracker-kinect-face/kinect_face.qrc (renamed from tracker-tobii-eyex/tobii-eyex-res.qrc)2
-rw-r--r--tracker-kinect-face/kinect_face_settings.cpp38
-rw-r--r--tracker-kinect-face/kinect_face_settings.h32
-rw-r--r--tracker-kinect-face/kinect_face_settings.ui74
-rw-r--r--tracker-kinect-face/kinect_face_tracker.cpp614
-rw-r--r--tracker-kinect-face/kinect_face_tracker.h121
-rw-r--r--tracker-kinect-face/lang/nl_NL.ts29
-rw-r--r--tracker-kinect-face/lang/ru_RU.ts29
-rw-r--r--tracker-kinect-face/lang/stub.ts29
-rw-r--r--tracker-kinect-face/lang/zh_CN.ts29
-rw-r--r--tracker-linux-joystick/CMakeLists.txt4
-rw-r--r--tracker-linux-joystick/ftnoir_tracker_linux_joystick.cpp87
-rw-r--r--tracker-linux-joystick/ftnoir_tracker_linux_joystick.h90
-rw-r--r--tracker-linux-joystick/ftnoir_tracker_linux_joystick_controls.ui492
-rw-r--r--tracker-linux-joystick/ftnoir_tracker_linux_joystick_dialog.cpp40
-rw-r--r--tracker-linux-joystick/lang/de_DE.ts86
-rw-r--r--tracker-linux-joystick/lang/nl_NL.ts86
-rw-r--r--tracker-linux-joystick/lang/ru_RU.ts86
-rw-r--r--tracker-linux-joystick/lang/stub.ts86
-rw-r--r--tracker-linux-joystick/lang/zh_CN.ts86
-rw-r--r--tracker-linux-joystick/linux_joystick.cpp65
-rw-r--r--tracker-neuralnet/BUILD.md20
-rw-r--r--tracker-neuralnet/CMakeLists.txt49
-rw-r--r--tracker-neuralnet/deadzone_filter.cpp173
-rw-r--r--tracker-neuralnet/deadzone_filter.h37
-rw-r--r--tracker-neuralnet/ftnoir_tracker_neuralnet.cpp976
-rw-r--r--tracker-neuralnet/ftnoir_tracker_neuralnet.h230
-rw-r--r--tracker-neuralnet/images/neuralnet.pngbin0 -> 595 bytes
-rw-r--r--tracker-neuralnet/lang/de_DE.ts172
-rw-r--r--tracker-neuralnet/lang/nl_NL.ts171
-rw-r--r--tracker-neuralnet/lang/ru_RU.ts174
-rw-r--r--tracker-neuralnet/lang/stub.ts171
-rw-r--r--tracker-neuralnet/lang/zh_CN.ts172
-rw-r--r--tracker-neuralnet/model_adapters.cpp416
-rw-r--r--tracker-neuralnet/model_adapters.h105
-rw-r--r--tracker-neuralnet/models/head-localizer.onnxbin0 -> 279403 bytes
-rw-r--r--tracker-neuralnet/models/head-pose-0.2-big.onnxbin0 -> 44760256 bytes
-rw-r--r--tracker-neuralnet/models/head-pose-0.2-small.onnxbin0 -> 12926209 bytes
-rw-r--r--tracker-neuralnet/models/head-pose-0.3-big-quantized.onnxbin0 -> 11385815 bytes
-rw-r--r--tracker-neuralnet/neuralnet-tracker.qrc5
-rw-r--r--tracker-neuralnet/neuralnet-trackercontrols.ui697
-rw-r--r--tracker-neuralnet/opencv_contrib.h120
-rw-r--r--tracker-neuralnet/preview.cpp135
-rw-r--r--tracker-neuralnet/preview.h60
-rw-r--r--tracker-neuralnet/redist/vcomp140.dllbin0 -> 151976 bytes
-rw-r--r--tracker-neuralnet/tests.cpp58
-rw-r--r--tracker-neuralnet/unscented_trafo.h132
-rw-r--r--tracker-pt/CMakeLists.txt13
-rw-r--r--tracker-pt/FTNoIR_PT_Controls.ui789
-rw-r--r--tracker-pt/export.hpp15
-rw-r--r--tracker-pt/ftnoir_tracker_pt.cpp223
-rw-r--r--tracker-pt/ftnoir_tracker_pt.h57
-rw-r--r--tracker-pt/ftnoir_tracker_pt_dialog.cpp149
-rw-r--r--tracker-pt/ftnoir_tracker_pt_dialog.h17
-rw-r--r--tracker-pt/lang/de_DE.ts378
-rw-r--r--tracker-pt/lang/nl_NL.ts205
-rw-r--r--tracker-pt/lang/ru_RU.ts205
-rw-r--r--tracker-pt/lang/stub.ts205
-rw-r--r--tracker-pt/lang/zh_CN.ts207
-rw-r--r--tracker-pt/module/CMakeLists.txt10
-rw-r--r--tracker-pt/module/camera.cpp149
-rw-r--r--tracker-pt/module/camera.h30
-rw-r--r--tracker-pt/module/export.hpp11
-rw-r--r--tracker-pt/module/frame.cpp66
-rw-r--r--tracker-pt/module/frame.hpp20
-rw-r--r--tracker-pt/module/lang/de_DE.ts11
-rw-r--r--tracker-pt/module/lang/nl_NL.ts7
-rw-r--r--tracker-pt/module/lang/ru_RU.ts7
-rw-r--r--tracker-pt/module/lang/stub.ts7
-rw-r--r--tracker-pt/module/lang/zh_CN.ts9
-rw-r--r--tracker-pt/module/module.cpp21
-rw-r--r--tracker-pt/module/module.hpp20
-rw-r--r--tracker-pt/module/point_extractor.cpp307
-rw-r--r--tracker-pt/module/point_extractor.h26
-rw-r--r--tracker-pt/point-filter.cpp72
-rw-r--r--tracker-pt/point-filter.hpp32
-rw-r--r--tracker-pt/point_tracker.cpp210
-rw-r--r--tracker-pt/point_tracker.h41
-rw-r--r--tracker-pt/pt-api.cpp76
-rw-r--r--tracker-pt/pt-api.hpp92
-rw-r--r--tracker-pt/pt-settings.hpp48
-rw-r--r--tracker-rift-025/CMakeLists.txt4
-rw-r--r--tracker-rift-025/ftnoir_rift_025.qrc7
-rw-r--r--tracker-rift-025/ftnoir_rift_clientcontrols_025.ui176
-rw-r--r--tracker-rift-025/ftnoir_tracker_rift_025.cpp118
-rw-r--r--tracker-rift-025/ftnoir_tracker_rift_025.h65
-rw-r--r--tracker-rift-025/ftnoir_tracker_rift_dialog_025.cpp25
-rw-r--r--tracker-rift-025/images/medium.pngbin5764 -> 0 bytes
-rw-r--r--tracker-rift-025/images/rift_medium.pngbin5764 -> 0 bytes
-rw-r--r--tracker-rift-025/images/rift_small.pngbin1212 -> 0 bytes
-rw-r--r--tracker-rift-025/images/rift_tiny.pngbin624 -> 0 bytes
-rw-r--r--tracker-rift-025/images/small.pngbin1212 -> 0 bytes
-rw-r--r--tracker-rift-025/images/tiny.pngbin624 -> 0 bytes
-rw-r--r--tracker-rift-042/CMakeLists.txt3
-rw-r--r--tracker-rift-042/ftnoir_rift_042.qrc7
-rw-r--r--tracker-rift-042/ftnoir_rift_clientcontrols_042.ui176
-rw-r--r--tracker-rift-042/ftnoir_tracker_rift_042.cpp99
-rw-r--r--tracker-rift-042/ftnoir_tracker_rift_042.h62
-rw-r--r--tracker-rift-042/ftnoir_tracker_rift_dialog_042.cpp25
-rw-r--r--tracker-rift-042/images/medium.pngbin5764 -> 0 bytes
-rw-r--r--tracker-rift-042/images/rift_medium.pngbin5764 -> 0 bytes
-rw-r--r--tracker-rift-042/images/rift_small.pngbin1212 -> 0 bytes
-rw-r--r--tracker-rift-042/images/rift_tiny.pngbin624 -> 0 bytes
-rw-r--r--tracker-rift-042/images/small.pngbin1212 -> 0 bytes
-rw-r--r--tracker-rift-042/images/tiny.pngbin624 -> 0 bytes
-rw-r--r--tracker-rift-042/lang/zh_CN.ts38
-rw-r--r--tracker-rift-080/CMakeLists.txt3
-rw-r--r--tracker-rift-080/ftnoir_rift_080.qrc7
-rw-r--r--tracker-rift-080/ftnoir_rift_clientcontrols_080.ui176
-rw-r--r--tracker-rift-080/ftnoir_tracker_rift_080.cpp113
-rw-r--r--tracker-rift-080/ftnoir_tracker_rift_080.h59
-rw-r--r--tracker-rift-080/ftnoir_tracker_rift_dialog_080.cpp25
-rw-r--r--tracker-rift-080/images/medium.pngbin5764 -> 0 bytes
-rw-r--r--tracker-rift-080/images/rift_medium.pngbin5764 -> 0 bytes
-rw-r--r--tracker-rift-080/images/rift_small.pngbin1212 -> 0 bytes
-rw-r--r--tracker-rift-080/images/rift_tiny.pngbin624 -> 0 bytes
-rw-r--r--tracker-rift-080/images/small.pngbin1212 -> 0 bytes
-rw-r--r--tracker-rift-080/images/tiny.pngbin624 -> 0 bytes
-rw-r--r--tracker-rift-080/lang/nl_NL.ts31
-rw-r--r--tracker-rift-080/lang/ru_RU.ts31
-rw-r--r--tracker-rift-080/lang/stub.ts31
-rw-r--r--tracker-rift-080/lang/zh_CN.ts31
-rw-r--r--tracker-rift-140/CMakeLists.txt17
-rw-r--r--tracker-rift-140/dialog.cpp7
-rw-r--r--tracker-rift-140/dialog.ui122
-rw-r--r--tracker-rift-140/lang/nl_NL.ts21
-rw-r--r--tracker-rift-140/lang/ru_RU.ts21
-rw-r--r--tracker-rift-140/lang/stub.ts21
-rw-r--r--tracker-rift-140/lang/zh_CN.ts23
-rw-r--r--tracker-rift-140/rift-140.cpp45
-rw-r--r--tracker-rift-140/rift-140.hpp27
-rw-r--r--tracker-rs/CMakeLists.txt4
-rw-r--r--tracker-rs/README.md2
-rw-r--r--tracker-rs/ftnoir_tracker_rs.cpp38
-rw-r--r--tracker-rs/ftnoir_tracker_rs.h12
-rw-r--r--tracker-rs/ftnoir_tracker_rs_controls.cpp11
-rw-r--r--tracker-rs/ftnoir_tracker_rs_controls.h3
-rw-r--r--tracker-rs/ftnoir_tracker_rs_controls.ui74
-rw-r--r--tracker-rs/ftnoir_tracker_rs_worker.cpp1
-rw-r--r--tracker-rs/ftnoir_tracker_rs_worker.h2
-rw-r--r--tracker-rs/imagewidget.cpp5
-rw-r--r--tracker-rs/imagewidget.h4
-rw-r--r--tracker-rs/lang/nl_NL.ts46
-rw-r--r--tracker-rs/lang/ru_RU.ts46
-rw-r--r--tracker-rs/lang/stub.ts46
-rw-r--r--tracker-rs/lang/zh_CN.ts48
-rw-r--r--tracker-rs/redist/RS_EULA.rtf500
-rw-r--r--tracker-rs/redist/intel_rs_sdk_runtime_websetup_10.0.26.0396.exebin1226416 -> 0 bytes
-rw-r--r--tracker-rs/rs_impl/CMakeLists.txt2
-rw-r--r--tracker-rs/rs_impl/attic/build.bat58
-rw-r--r--tracker-rs/rs_impl/attic/build_common.bat42
-rw-r--r--tracker-rs/rs_impl/ftnoir_tracker_rs_impl.cpp18
-rw-r--r--tracker-rs/rs_impl/ftnoir_tracker_rs_impl.h15
-rw-r--r--tracker-s2bot/ftnoir_tracker_s2bot.cpp27
-rw-r--r--tracker-s2bot/ftnoir_tracker_s2bot.h5
-rw-r--r--tracker-s2bot/lang/de_DE.ts90
-rw-r--r--tracker-s2bot/lang/nl_NL.ts7
-rw-r--r--tracker-s2bot/lang/ru_RU.ts7
-rw-r--r--tracker-s2bot/lang/stub.ts7
-rw-r--r--tracker-s2bot/lang/zh_CN.ts41
-rw-r--r--tracker-steamvr/CMakeLists.txt28
-rw-r--r--tracker-steamvr/lang/nl_NL.ts7
-rw-r--r--tracker-steamvr/lang/ru_RU.ts7
-rw-r--r--tracker-steamvr/lang/stub.ts7
-rw-r--r--tracker-steamvr/lang/zh_CN.ts9
-rw-r--r--tracker-steamvr/steamvr.cpp133
-rw-r--r--tracker-steamvr/steamvr.hpp44
-rw-r--r--tracker-test/lang/de_DE.ts22
-rw-r--r--tracker-test/lang/nl_NL.ts7
-rw-r--r--tracker-test/lang/ru_RU.ts7
-rw-r--r--tracker-test/lang/stub.ts7
-rw-r--r--tracker-test/lang/zh_CN.ts9
-rw-r--r--tracker-test/test.cpp62
-rw-r--r--tracker-test/test.h11
-rw-r--r--tracker-tobii-eyex/CMakeLists.txt24
-rw-r--r--tracker-tobii-eyex/images/tobii-eyex-logo.pngbin402 -> 0 bytes
-rw-r--r--tracker-tobii-eyex/lang/zh_CN.ts56
-rw-r--r--tracker-tobii-eyex/tobii-eyex-dialog.cpp25
-rw-r--r--tracker-tobii-eyex/tobii-eyex-dialog.hpp21
-rw-r--r--tracker-tobii-eyex/tobii-eyex-dialog.ui484
-rw-r--r--tracker-tobii-eyex/tobii-eyex.cpp312
-rw-r--r--tracker-tobii-eyex/tobii-eyex.hpp87
-rw-r--r--tracker-tobii-eyex/tobii-settings.hpp36
-rw-r--r--tracker-tobii/CMakeLists.txt22
-rw-r--r--tracker-tobii/lang/nl_NL.ts22
-rw-r--r--tracker-tobii/lang/ru_RU.ts22
-rw-r--r--tracker-tobii/lang/stub.ts22
-rw-r--r--tracker-tobii/lang/zh_CN.ts22
-rw-r--r--tracker-tobii/tobii.cpp121
-rw-r--r--tracker-tobii/tobii.h61
-rw-r--r--tracker-tobii/tobii.qrc5
-rw-r--r--tracker-tobii/tobii.ui67
-rw-r--r--tracker-tobii/tobii_dialog.cpp20
-rw-r--r--tracker-tobii/tobii_logo.pngbin0 -> 1768 bytes
-rw-r--r--tracker-trackhat/CMakeLists.txt16
-rw-r--r--tracker-trackhat/camera.cpp140
-rw-r--r--tracker-trackhat/dialog.cpp165
-rw-r--r--tracker-trackhat/dialog.hpp36
-rw-r--r--tracker-trackhat/dialog.ui602
-rw-r--r--tracker-trackhat/extractor.cpp21
-rw-r--r--tracker-trackhat/frame.cpp128
-rw-r--r--tracker-trackhat/handle.cpp72
-rw-r--r--tracker-trackhat/images/trackhat-64x64.pngbin0 -> 1430 bytes
-rw-r--r--tracker-trackhat/images/trackhat.icobin0 -> 3758 bytes
-rw-r--r--tracker-trackhat/images/trackhat.pngbin0 -> 14581 bytes
-rw-r--r--tracker-trackhat/lang/nl_NL.ts154
-rw-r--r--tracker-trackhat/lang/ru_RU.ts154
-rw-r--r--tracker-trackhat/lang/stub.ts154
-rw-r--r--tracker-trackhat/lang/zh_CN.ts154
-rw-r--r--tracker-trackhat/led.cpp66
-rw-r--r--tracker-trackhat/metadata.cpp39
-rw-r--r--tracker-trackhat/metadata.hpp21
-rw-r--r--tracker-trackhat/settings.cpp144
-rw-r--r--tracker-trackhat/tracker_trackhat.qrc5
-rw-r--r--tracker-trackhat/trackhat-res.qrc5
-rw-r--r--tracker-trackhat/trackhat.hpp209
-rw-r--r--tracker-udp/ftnoir_tracker_udp.cpp24
-rw-r--r--tracker-udp/ftnoir_tracker_udp.h7
-rw-r--r--tracker-udp/lang/de_DE.ts65
-rw-r--r--tracker-udp/lang/nl_NL.ts7
-rw-r--r--tracker-udp/lang/ru_RU.ts7
-rw-r--r--tracker-udp/lang/stub.ts7
-rw-r--r--tracker-udp/lang/zh_CN.ts9
-rw-r--r--tracker-wii/CMakeLists.txt13
-rw-r--r--tracker-wii/lang/nl_NL.ts7
-rw-r--r--tracker-wii/lang/ru_RU.ts7
-rw-r--r--tracker-wii/lang/stub.ts7
-rw-r--r--tracker-wii/lang/zh_CN.ts9
-rw-r--r--tracker-wii/wii_camera.cpp122
-rw-r--r--tracker-wii/wii_camera.h48
-rw-r--r--tracker-wii/wii_frame.cpp19
-rw-r--r--tracker-wii/wii_frame.hpp9
-rw-r--r--tracker-wii/wii_module.cpp7
-rw-r--r--tracker-wii/wii_module.hpp11
-rw-r--r--tracker-wii/wii_point_extractor.cpp90
-rw-r--r--tracker-wii/wii_point_extractor.h17
-rw-r--r--tracker-wii/wiiyourself/CMakeLists.txt3
-rw-r--r--tracker-wii/wiiyourself/lang/zh_CN.ts2
-rw-r--r--tracker-wii/wiiyourself/wiimote.cpp191
-rw-r--r--tracker-wii/wiiyourself/wiimote.h7
-rw-r--r--variant/default/CMakeLists.txt9
-rw-r--r--variant/default/_variant.cmake27
-rw-r--r--variant/default/main-window.cpp929
-rw-r--r--variant/default/main-window.ui1459
-rw-r--r--variant/trackmouse/CMakeLists.txt9
-rw-r--r--variant/trackmouse/_variant.cmake19
-rw-r--r--variant/trackmouse/images/start.pngbin1400 -> 0 bytes
-rw-r--r--variant/trackmouse/images/stop.pngbin5790 -> 0 bytes
-rw-r--r--variant/trackmouse/main.cpp18
-rw-r--r--variant/trackmouse/trackmouse-res.qrc6
-rw-r--r--variant/trackmouse/trackmouse-settings.cpp110
-rw-r--r--variant/trackmouse/trackmouse.icobin67134 -> 0 bytes
-rw-r--r--variant/trackmouse/trackmouse.rc2
-rw-r--r--variant/trackmouse/window.cpp22
-rw-r--r--variant/trackmouse/window.hpp17
-rw-r--r--variant/trackmouse/window.ui420
-rw-r--r--video-opencv/CMakeLists.txt13
-rw-r--r--video-opencv/impl-camera.cpp104
-rw-r--r--video-opencv/impl-metadata.cpp46
-rw-r--r--video-opencv/impl.hpp55
-rw-r--r--video-opencv/lang/de_DE.ts4
-rw-r--r--video-opencv/lang/nl_NL.ts (renamed from ext-falcon-bms-linear-acc/lang/nl_NL.ts)0
-rw-r--r--video-opencv/lang/ru_RU.ts (renamed from ext-falcon-bms-linear-acc/lang/ru_RU.ts)0
-rw-r--r--video-opencv/lang/stub.ts (renamed from ext-falcon-bms-linear-acc/lang/stub.ts)0
-rw-r--r--video-opencv/lang/zh_CN.ts4
-rw-r--r--video-opencv/video-property-page.cpp (renamed from cv/video-property-page.cpp)56
-rw-r--r--video-opencv/video-property-page.hpp (renamed from cv/video-property-page.hpp)8
-rw-r--r--video-ps3eye/CMakeLists.txt69
-rw-r--r--video-ps3eye/dialog.ui139
-rw-r--r--video-ps3eye/lang/nl_NL.ts (renamed from tracker-rift-042/lang/nl_NL.ts)22
-rw-r--r--video-ps3eye/lang/ru_RU.ts (renamed from tracker-rift-042/lang/ru_RU.ts)22
-rw-r--r--video-ps3eye/lang/stub.ts (renamed from tracker-rift-042/lang/stub.ts)22
-rw-r--r--video-ps3eye/lang/zh_CN.ts38
-rw-r--r--video-ps3eye/module.cpp307
-rw-r--r--video-ps3eye/module.hpp82
m---------video-ps3eye/ps3eye-driver0
-rw-r--r--video-ps3eye/shm-layout.hpp39
-rw-r--r--video-ps3eye/shm.cxx2
-rw-r--r--video-ps3eye/shm.hpp4
-rw-r--r--video-ps3eye/wrapper.cxx120
-rw-r--r--video/CMakeLists.txt1
-rw-r--r--video/camera.cpp89
-rw-r--r--video/camera.hpp111
-rw-r--r--video/export.hpp11
-rw-r--r--video/lang/de_DE.ts4
-rw-r--r--video/lang/nl_NL.ts (renamed from variant/trackmouse/lang/nl_NL.ts)0
-rw-r--r--video/lang/ru_RU.ts (renamed from variant/trackmouse/lang/ru_RU.ts)0
-rw-r--r--video/lang/stub.ts (renamed from variant/trackmouse/lang/stub.ts)0
-rw-r--r--video/lang/zh_CN.ts4
-rw-r--r--video/video-widget.cpp90
-rw-r--r--video/video-widget.hpp49
-rw-r--r--x-plane-plugin/CMakeLists.txt48
-rw-r--r--x-plane-plugin/plugin.c112
885 files changed, 45405 insertions, 24709 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 00000000..eb080eb9
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,47 @@
+BasedOnStyle: LLVM
+AccessModifierOffset: -4
+AlignEscapedNewlines: Right
+AlignOperands: Align
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: Never
+AllowShortFunctionsOnASingleLine: InlineOnly
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: MultiLine
+BinPackParameters: false
+BreakBeforeBinaryOperators: NonAssignment
+BreakBeforeBraces: Allman
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+ColumnLimit: 150
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: false
+DerivePointerAlignment: false
+IndentCaseLabels: false
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+KeepEmptyLinesAtTheStartOfBlocks: false
+Language: Cpp
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+PenaltyBreakBeforeFirstCallParameter: 45
+PenaltyBreakComment: 40
+PenaltyBreakFirstLessLess: 50
+PenaltyBreakString: 35
+PenaltyExcessCharacter: 1
+PenaltyReturnTypeOnItsOwnLine: 20
+PenaltyBreakAssignment: 15
+PointerAlignment: Left
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+Standard: Latest
+TabWidth: 4
+UseTab: Never
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 00000000..df2a6e10
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,134 @@
+Checks: '
+*,
+-altera-*,
+-android-*,
+-clang-analyzer-osx.*,
+-fuchsia-*,
+-google-*,
+-llvm-*,
+-llvmlibc-*,
+-objc-*,
+-bugprone-assignment-in-if-condition,
+-bugprone-easily-swappable-parameters,
+bugprone-exception-escape,
+-bugprone-implicit-widening-of-multiplication-result,
+-bugprone-narrowing-conversions,
+-cert-*,
+llvm-namespace-comment,
+clang-analyzer-core.CallAndMessage,
+clang-analyzer-deadcode.DeadStores,
+-clang-diagnostic-nonportable-include-path,
+-cppcoreguidelines-avoid-c-arrays,
+-cppcoreguidelines-avoid-capture-default-when-capturing-this,
+-cppcoreguidelines-avoid-do-while,
+-cppcoreguidelines-avoid-goto,
+-cppcoreguidelines-avoid-magic-numbers,
+-cppcoreguidelines-c-copy-assignment-signature,
+-cppcoreguidelines-init-variables,
+-cppcoreguidelines-macro-usage,
+-cppcoreguidelines-narrowing-conversions,
+-cppcoreguidelines-non-private-member-variables-in-classes,
+-cppcoreguidelines-owning-memory,
+-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
+-cppcoreguidelines-pro-bounds-constant-array-index,
+-cppcoreguidelines-pro-bounds-pointer-arithmetic,
+-cppcoreguidelines-pro-type-const-cast,
+-cppcoreguidelines-pro-type-cstyle-cast,
+-cppcoreguidelines-pro-type-reinterpret-cast,
+-cppcoreguidelines-pro-type-static-cast-downcast,
+-cppcoreguidelines-pro-type-union-access,
+-cppcoreguidelines-pro-type-vararg,
+-cppcoreguidelines-special-member-functions,
+-google-readability-braces-around-statements,
+-google-readability-casting,
+-hicpp-avoid-c-arrays,
+-hicpp-avoid-goto,
+-hicpp-braces-around-statements,
+-hicpp-exception-baseclass,
+-hicpp-explicit-conversions,
+-hicpp-function-size,
+-hicpp-invalid-access-moved,
+-hicpp-member-init,
+-hicpp-move-const-arg,
+-hicpp-named-parameter,
+-hicpp-new-delete-operators,
+-hicpp-no-array-decay,
+-hicpp-signed-bitwise,
+-hicpp-special-member-functions,
+-hicpp-uppercase-literal-suffix,
+-hicpp-use-auto,
+-hicpp-use-equals-default,
+-hicpp-use-equals-delete,
+-hicpp-use-override,
+-hicpp-vararg,
+-misc-non-private-member-variables-in-classes,
+-misc-use-anonymous-namespace,
+-modernize-avoid-c-arrays,
+-modernize-loop-convert,
+-modernize-pass-by-value,
+-modernize-raw-string-literal,
+-modernize-redundant-void-arg,
+-modernize-return-braced-init-list,
+-modernize-use-auto,
+modernize-use-equals-default,
+-modernize-use-nodiscard,
+-modernize-use-trailing-return-type,
+-modernize-use-using,
+-performance-no-int-to-ptr,
+-readability-braces-around-statements,
+-readability-else-after-return,
+readability-function-cognitive-complexity,
+-readability-function-size,
+-readability-identifier-length,
+-readability-implicit-bool-conversion,
+-readability-inconsistent-declaration-parameter-name,
+-readability-isolate-declaration,
+-readability-magic-numbers,
+readability-make-member-function-const,
+-readability-named-parameter,
+-readability-redundant-access-specifiers,
+-readability-simplify-boolean-expr,
+-readability-static-accessed-through-instance,
+-readability-uppercase-literal-suffix,
+-readability-use-anyofallof,
+-*-avoid-unconditional-preprocessor-if,
+-*-braces-around-statements,
+-*-incorrect-roundings,
+-*-uppercase-literal-suffix,
+-*-use-emplace,
+-*-use-std-print,
+-*-use-trailing-return-type,
+-*-vararg,
+'
+
+AnalyzeTemporaryDtors: true
+
+CheckOptions:
+ - key: cert-dcl59-cpp.HeaderFileExtensions
+ value: ',h,hpp'
+ - key: cppcoreguidelines-pro-type-member-init.IgnoreArrays
+ value: '1'
+ - key: readability-simplify-boolean-expr.ChainedConditionalAssignment
+ value: '1'
+ - key: readability-simplify-boolean-expr.ChainedConditionalReturn
+ value: '1'
+ - key: readability-function-size.BranchThreshold
+ value: '15'
+ - key: readability-function-size.LineThreshold
+ value: '120'
+ - key: readability-function-size.NestingThreshold
+ value: '6'
+ - key: readability-function-size.ParameterThreshold
+ value: '8'
+ - key: readability-function-size.StatementThreshold
+ value: '90'
+ - key: readability-function-size.VariableThreshold
+ value: '10'
+ - key: readability-function-cognitive-complexity.IgnoreMacros
+ value: '1'
+ - key: readability-function-cognitive-complexity.DescribeBasicIncrements
+ value: '1'
+ - key: misc-const-correctness.AnalyzeValues
+ value: '0'
+
+# vim: ft=yaml
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..51ffe580
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,18 @@
+# https://editorconfig.org/
+
+root = true
+
+[*]
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+tab_width = 4
+
+[Makefile]
+indent_style = tab
+
+[*.{bat,cmd}]
+end_of_line = crlf
diff --git a/.gitattributes b/.gitattributes
index 9be84023..462acc88 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,12 +1,22 @@
-*.cpp text=auto eol=lf
-*.hpp text=auto eol=lf
-*.c text=auto eol=lf
-*.h text=auto eol=lf
-*.cxx text=auto eol=lf
-*.txt text=auto eol=lf
-*.ui text=auto eol=lf
-*.qrc text=auto eol=lf
-*.cc text=auto eol=lf
-*.md text=auto eol=lf
-*.csv text=auto eol=lf
-* text=auto eol=lf
+*.[ch]pp text eol=lf
+*.[ch] text eol=lf
+*.[ch]xx text eol=lf
+*.cc text eol=lf
+*.rc text eol=lf
+
+*.git?* text eol=lf
+
+*.txt text eol=lf
+*.cmake text eol=lf
+
+*.md text eol=lf
+*.csv text eol=lf
+*.json text eol=lf
+
+*.ui text eol=lf
+*.qrc text eol=lf
+*.ts text eol=lf
+*.in text eol=lf
+
+*.cmd text eol=crlf
+*.bat text eol=crlf
diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 115e76df..115e76df 100644
--- a/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
diff --git a/.github/workflows/build-windows.bat b/.github/workflows/build-windows.bat
new file mode 100644
index 00000000..4571683e
--- /dev/null
+++ b/.github/workflows/build-windows.bat
@@ -0,0 +1,3 @@
+set path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files\CMake\bin;C:\Program Files\Git\cmd;%GITHUB_WORKSPACE%/ninja-build
+"C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat" >nul && %*
+exit /b %ERRORLEVEL%
diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml
new file mode 100644
index 00000000..fc53f6d7
--- /dev/null
+++ b/.github/workflows/cmake.yml
@@ -0,0 +1,66 @@
+name: CMake
+
+on:
+ - push
+ - pull_request
+
+env:
+ # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
+ BUILD_TYPE: Release
+
+jobs:
+ build:
+ # The CMake configure and build commands are platform agnostic and should work equally
+ # well on Windows or Mac. You can convert this to a matrix build if you need
+ # cross-platform coverage.
+ # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [macos-latest, ubuntu-latest, windows-latest]
+ include:
+ - os: macos-latest
+ cmake: cmake
+ - os: ubuntu-latest
+ cmake: cmake
+ - os: windows-latest
+ cmake: .\.github\workflows\build-windows.bat cmake
+
+ steps:
+ - name: clone opentrack/opentrack
+ uses: actions/checkout@v2
+
+ - name: clone opentrack/depends
+ uses: actions/checkout@v2
+ with:
+ repository: opentrack/opentrack-depends
+ submodules: true
+ path: opentrack-depends
+
+ - uses: seanmiddleditch/gha-setup-ninja@master
+
+ - name: Install Linux Dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install libproc2-dev libopencv-dev libopencv-dev wine64-tools
+ sudo apt-get install qttools5-dev qtbase5-dev libqt5serialport5-dev qtbase5-private-dev
+ if: matrix.os == 'ubuntu-latest'
+
+ - name: Install Qt
+ uses: jurplel/install-qt-action@v3
+ with:
+ archives: qtbase qtimageformats qtgamepad qttools qtserialport qtmultimedia
+ if: matrix.os != 'ubuntu-latest'
+
+ - name: Configure
+ run: ${{matrix.cmake}} -GNinja -S ${{github.workspace}}/ -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DQt5_DIR=${{env.Qt5_DIR}} -DQt5Gui_DIR=${{env.Qt5_DIR}}/lib/cmake/Qt5Gui
+
+ - name: Build
+ run: ${{matrix.cmake}} --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target install
+
+# - name: Upload build
+# uses: actions/upload-artifact@v3
+# with:
+# name: buildoutput
+# path: ${{github.workspace}}/build/
diff --git a/.github/workflows/linux-package.yml b/.github/workflows/linux-package.yml
new file mode 100644
index 00000000..bc622b6f
--- /dev/null
+++ b/.github/workflows/linux-package.yml
@@ -0,0 +1,77 @@
+name: Linuax Package
+on:
+ push:
+ tags:
+ - 'opentrack-*'
+ workflow_dispatch:
+env:
+ build_type: Release
+ onnx_version: 1.21.0
+ onnx_prefix: onnxruntime-linux-x64
+ file_name: '' # to disable warning in case of a linter
+ Qt5_DIR: '' # to disable warning in case of a linter
+jobs:
+ Package:
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Clone opentrack/opentrack
+ uses: actions/checkout@v4
+ - name: Clone opentrack/depends
+ uses: actions/checkout@v4
+ with:
+ repository: opentrack/opentrack-depends
+ submodules: true
+ path: opentrack-depends
+
+ - name: Install Dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install libprocps-dev libopencv-dev wine64-tools
+ wget -q https://github.com/microsoft/onnxruntime/releases/download/v${{env.onnx_version}}/${{env.onnx_prefix}}-${{env.onnx_version}}.tgz
+ tar xzf ${{env.onnx_prefix}}-${{env.onnx_version}}.tgz
+ - name: Install Qt
+ uses: jurplel/install-qt-action@v4
+ with:
+ version: 5.15
+ archives: icu qtbase qttools qtserialport
+
+ - name: Configure and Build
+ run: |
+ cmake -S . -B build \
+ -DCMAKE_BUILD_TYPE=${{env.build_type}} \
+ -DONNXRuntime_DIR=${{env.onnx_prefix}}-${{env.onnx_version}} \
+ -DQt5_DIR=${{env.Qt5_DIR}}
+ cmake --build build --config ${{env.build_type}} --target install -- -j $(nproc)
+
+ - name: Pack
+ run: |
+ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/${{env.onnx_prefix}}-${{env.onnx_version}}/lib
+
+ # required by linuxdeployqt
+ sudo apt-get install libfuse2
+ wget -q https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage
+ chmod +x linuxdeployqt-*.AppImage
+ cd build/install/
+ ln -s $(pwd)/libexec/opentrack $(pwd)/lib
+ ../../linuxdeployqt-*.AppImage bin/opentrack -bundle-non-qt-libs -unsupported-allow-new-glibc
+ rm lib
+
+ echo '#!/bin/sh' >> opentrack
+ echo 'CURRENT_PATH="$(dirname "$(readlink -f "$0")")"' >> opentrack
+ echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CURRENT_PATH/libexec/opentrack' >> opentrack
+ echo '"$CURRENT_PATH/bin/opentrack" "$@"' >> opentrack
+ chmod +x opentrack
+
+ if [[ ${{github.ref_name}} =~ ^opentrack.*$ ]]; then
+ file_name=${{github.ref_name}}-linux.tar.gz
+ else
+ file_name=opentrack-dev-linux.tar.gz
+ fi
+ echo "file_name=$file_name" >> $GITHUB_ENV
+
+ tar czf ../../$file_name .
+ - name: Upload
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{env.file_name}}
+ path: ${{env.file_name}}
diff --git a/.gitignore b/.gitignore
index be11b2be..c8503cb5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,11 @@
-/CMakeLists.txt.user
-*~
-/build*
-/installer/Output
-# merely git cola saves there
-/patches/
+/build*/
+/build
+/build-*
.DS_Store
-*.obj
-*.exp
-*.pdb
-/.idea/
/sdk-paths-*.cmake
+/.idea/
/.vs/
+/CMakeSettings.json
+.gtm/
+.vscode
+.history
diff --git a/.gitmodules b/.gitmodules
index e69de29b..cad06181 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "video-ps3eye/ps3eye-driver"]
+ path = video-ps3eye/ps3eye-driver
+ url = https://github.com/opentrack/PS3EYEDriver
diff --git a/3rdparty-notices/EIGEN-COPYING.txt b/3rdparty-notices/EIGEN-COPYING.txt
deleted file mode 100644
index c203fff6..00000000
--- a/3rdparty-notices/EIGEN-COPYING.txt
+++ /dev/null
@@ -1,379 +0,0 @@
-The opentrack project is using the Eigen3 linear algebra library with site at
-<http://eigen.tuxfamily.org/>
-
-We're only using the MPL2-licensed bits. This is to avoid copyleft. License
-follows:
-
-Mozilla Public License Version 2.0
-==================================
-
-1. Definitions
---------------
-
-1.1. "Contributor"
- means each individual or legal entity that creates, contributes to
- the creation of, or owns Covered Software.
-
-1.2. "Contributor Version"
- means the combination of the Contributions of others (if any) used
- by a Contributor and that particular Contributor's Contribution.
-
-1.3. "Contribution"
- means Covered Software of a particular Contributor.
-
-1.4. "Covered Software"
- means Source Code Form to which the initial Contributor has attached
- the notice in Exhibit A, the Executable Form of such Source Code
- Form, and Modifications of such Source Code Form, in each case
- including portions thereof.
-
-1.5. "Incompatible With Secondary Licenses"
- means
-
- (a) that the initial Contributor has attached the notice described
- in Exhibit B to the Covered Software; or
-
- (b) that the Covered Software was made available under the terms of
- version 1.1 or earlier of the License, but not also under the
- terms of a Secondary License.
-
-1.6. "Executable Form"
- means any form of the work other than Source Code Form.
-
-1.7. "Larger Work"
- means a work that combines Covered Software with other material, in
- a separate file or files, that is not Covered Software.
-
-1.8. "License"
- means this document.
-
-1.9. "Licensable"
- means having the right to grant, to the maximum extent possible,
- whether at the time of the initial grant or subsequently, any and
- all of the rights conveyed by this License.
-
-1.10. "Modifications"
- means any of the following:
-
- (a) any file in Source Code Form that results from an addition to,
- deletion from, or modification of the contents of Covered
- Software; or
-
- (b) any new file in Source Code Form that contains any Covered
- Software.
-
-1.11. "Patent Claims" of a Contributor
- means any patent claim(s), including without limitation, method,
- process, and apparatus claims, in any patent Licensable by such
- Contributor that would be infringed, but for the grant of the
- License, by the making, using, selling, offering for sale, having
- made, import, or transfer of either its Contributions or its
- Contributor Version.
-
-1.12. "Secondary License"
- means either the GNU General Public License, Version 2.0, the GNU
- Lesser General Public License, Version 2.1, the GNU Affero General
- Public License, Version 3.0, or any later versions of those
- licenses.
-
-1.13. "Source Code Form"
- means the form of the work preferred for making modifications.
-
-1.14. "You" (or "Your")
- means an individual or a legal entity exercising rights under this
- License. For legal entities, "You" includes any entity that
- controls, is controlled by, or is under common control with You. For
- purposes of this definition, "control" means (a) the power, direct
- or indirect, to cause the direction or management of such entity,
- whether by contract or otherwise, or (b) ownership of more than
- fifty percent (50%) of the outstanding shares or beneficial
- ownership of such entity.
-
-2. License Grants and Conditions
---------------------------------
-
-2.1. Grants
-
-Each Contributor hereby grants You a world-wide, royalty-free,
-non-exclusive license:
-
-(a) under intellectual property rights (other than patent or trademark)
- Licensable by such Contributor to use, reproduce, make available,
- modify, display, perform, distribute, and otherwise exploit its
- Contributions, either on an unmodified basis, with Modifications, or
- as part of a Larger Work; and
-
-(b) under Patent Claims of such Contributor to make, use, sell, offer
- for sale, have made, import, and otherwise transfer either its
- Contributions or its Contributor Version.
-
-2.2. Effective Date
-
-The licenses granted in Section 2.1 with respect to any Contribution
-become effective for each Contribution on the date the Contributor first
-distributes such Contribution.
-
-2.3. Limitations on Grant Scope
-
-The licenses granted in this Section 2 are the only rights granted under
-this License. No additional rights or licenses will be implied from the
-distribution or licensing of Covered Software under this License.
-Notwithstanding Section 2.1(b) above, no patent license is granted by a
-Contributor:
-
-(a) for any code that a Contributor has removed from Covered Software;
- or
-
-(b) for infringements caused by: (i) Your and any other third party's
- modifications of Covered Software, or (ii) the combination of its
- Contributions with other software (except as part of its Contributor
- Version); or
-
-(c) under Patent Claims infringed by Covered Software in the absence of
- its Contributions.
-
-This License does not grant any rights in the trademarks, service marks,
-or logos of any Contributor (except as may be necessary to comply with
-the notice requirements in Section 3.4).
-
-2.4. Subsequent Licenses
-
-No Contributor makes additional grants as a result of Your choice to
-distribute the Covered Software under a subsequent version of this
-License (see Section 10.2) or under the terms of a Secondary License (if
-permitted under the terms of Section 3.3).
-
-2.5. Representation
-
-Each Contributor represents that the Contributor believes its
-Contributions are its original creation(s) or it has sufficient rights
-to grant the rights to its Contributions conveyed by this License.
-
-2.6. Fair Use
-
-This License is not intended to limit any rights You have under
-applicable copyright doctrines of fair use, fair dealing, or other
-equivalents.
-
-2.7. Conditions
-
-Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
-in Section 2.1.
-
-3. Responsibilities
--------------------
-
-3.1. Distribution of Source Form
-
-All distribution of Covered Software in Source Code Form, including any
-Modifications that You create or to which You contribute, must be under
-the terms of this License. You must inform recipients that the Source
-Code Form of the Covered Software is governed by the terms of this
-License, and how they can obtain a copy of this License. You may not
-attempt to alter or restrict the recipients' rights in the Source Code
-Form.
-
-3.2. Distribution of Executable Form
-
-If You distribute Covered Software in Executable Form then:
-
-(a) such Covered Software must also be made available in Source Code
- Form, as described in Section 3.1, and You must inform recipients of
- the Executable Form how they can obtain a copy of such Source Code
- Form by reasonable means in a timely manner, at a charge no more
- than the cost of distribution to the recipient; and
-
-(b) You may distribute such Executable Form under the terms of this
- License, or sublicense it under different terms, provided that the
- license for the Executable Form does not attempt to limit or alter
- the recipients' rights in the Source Code Form under this License.
-
-3.3. Distribution of a Larger Work
-
-You may create and distribute a Larger Work under terms of Your choice,
-provided that You also comply with the requirements of this License for
-the Covered Software. If the Larger Work is a combination of Covered
-Software with a work governed by one or more Secondary Licenses, and the
-Covered Software is not Incompatible With Secondary Licenses, this
-License permits You to additionally distribute such Covered Software
-under the terms of such Secondary License(s), so that the recipient of
-the Larger Work may, at their option, further distribute the Covered
-Software under the terms of either this License or such Secondary
-License(s).
-
-3.4. Notices
-
-You may not remove or alter the substance of any license notices
-(including copyright notices, patent notices, disclaimers of warranty,
-or limitations of liability) contained within the Source Code Form of
-the Covered Software, except that You may alter any license notices to
-the extent required to remedy known factual inaccuracies.
-
-3.5. Application of Additional Terms
-
-You may choose to offer, and to charge a fee for, warranty, support,
-indemnity or liability obligations to one or more recipients of Covered
-Software. However, You may do so only on Your own behalf, and not on
-behalf of any Contributor. You must make it absolutely clear that any
-such warranty, support, indemnity, or liability obligation is offered by
-You alone, and You hereby agree to indemnify every Contributor for any
-liability incurred by such Contributor as a result of warranty, support,
-indemnity or liability terms You offer. You may include additional
-disclaimers of warranty and limitations of liability specific to any
-jurisdiction.
-
-4. Inability to Comply Due to Statute or Regulation
----------------------------------------------------
-
-If it is impossible for You to comply with any of the terms of this
-License with respect to some or all of the Covered Software due to
-statute, judicial order, or regulation then You must: (a) comply with
-the terms of this License to the maximum extent possible; and (b)
-describe the limitations and the code they affect. Such description must
-be placed in a text file included with all distributions of the Covered
-Software under this License. Except to the extent prohibited by statute
-or regulation, such description must be sufficiently detailed for a
-recipient of ordinary skill to be able to understand it.
-
-5. Termination
---------------
-
-5.1. The rights granted under this License will terminate automatically
-if You fail to comply with any of its terms. However, if You become
-compliant, then the rights granted under this License from a particular
-Contributor are reinstated (a) provisionally, unless and until such
-Contributor explicitly and finally terminates Your grants, and (b) on an
-ongoing basis, if such Contributor fails to notify You of the
-non-compliance by some reasonable means prior to 60 days after You have
-come back into compliance. Moreover, Your grants from a particular
-Contributor are reinstated on an ongoing basis if such Contributor
-notifies You of the non-compliance by some reasonable means, this is the
-first time You have received notice of non-compliance with this License
-from such Contributor, and You become compliant prior to 30 days after
-Your receipt of the notice.
-
-5.2. If You initiate litigation against any entity by asserting a patent
-infringement claim (excluding declaratory judgment actions,
-counter-claims, and cross-claims) alleging that a Contributor Version
-directly or indirectly infringes any patent, then the rights granted to
-You by any and all Contributors for the Covered Software under Section
-2.1 of this License shall terminate.
-
-5.3. In the event of termination under Sections 5.1 or 5.2 above, all
-end user license agreements (excluding distributors and resellers) which
-have been validly granted by You or Your distributors under this License
-prior to termination shall survive termination.
-
-************************************************************************
-* *
-* 6. Disclaimer of Warranty *
-* ------------------------- *
-* *
-* Covered Software is provided under this License on an "as is" *
-* basis, without warranty of any kind, either expressed, implied, or *
-* statutory, including, without limitation, warranties that the *
-* Covered Software is free of defects, merchantable, fit for a *
-* particular purpose or non-infringing. The entire risk as to the *
-* quality and performance of the Covered Software is with You. *
-* Should any Covered Software prove defective in any respect, You *
-* (not any Contributor) assume the cost of any necessary servicing, *
-* repair, or correction. This disclaimer of warranty constitutes an *
-* essential part of this License. No use of any Covered Software is *
-* authorized under this License except under this disclaimer. *
-* *
-************************************************************************
-
-************************************************************************
-* *
-* 7. Limitation of Liability *
-* -------------------------- *
-* *
-* Under no circumstances and under no legal theory, whether tort *
-* (including negligence), contract, or otherwise, shall any *
-* Contributor, or anyone who distributes Covered Software as *
-* permitted above, be liable to You for any direct, indirect, *
-* special, incidental, or consequential damages of any character *
-* including, without limitation, damages for lost profits, loss of *
-* goodwill, work stoppage, computer failure or malfunction, or any *
-* and all other commercial damages or losses, even if such party *
-* shall have been informed of the possibility of such damages. This *
-* limitation of liability shall not apply to liability for death or *
-* personal injury resulting from such party's negligence to the *
-* extent applicable law prohibits such limitation. Some *
-* jurisdictions do not allow the exclusion or limitation of *
-* incidental or consequential damages, so this exclusion and *
-* limitation may not apply to You. *
-* *
-************************************************************************
-
-8. Litigation
--------------
-
-Any litigation relating to this License may be brought only in the
-courts of a jurisdiction where the defendant maintains its principal
-place of business and such litigation shall be governed by laws of that
-jurisdiction, without reference to its conflict-of-law provisions.
-Nothing in this Section shall prevent a party's ability to bring
-cross-claims or counter-claims.
-
-9. Miscellaneous
-----------------
-
-This License represents the complete agreement concerning the subject
-matter hereof. If any provision of this License is held to be
-unenforceable, such provision shall be reformed only to the extent
-necessary to make it enforceable. Any law or regulation which provides
-that the language of a contract shall be construed against the drafter
-shall not be used to construe this License against a Contributor.
-
-10. Versions of the License
----------------------------
-
-10.1. New Versions
-
-Mozilla Foundation is the license steward. Except as provided in Section
-10.3, no one other than the license steward has the right to modify or
-publish new versions of this License. Each version will be given a
-distinguishing version number.
-
-10.2. Effect of New Versions
-
-You may distribute the Covered Software under the terms of the version
-of the License under which You originally received the Covered Software,
-or under the terms of any subsequent version published by the license
-steward.
-
-10.3. Modified Versions
-
-If you create software not governed by this License, and you want to
-create a new license for such software, you may create and use a
-modified version of this License if you rename the license and remove
-any references to the name of the license steward (except to note that
-such modified license differs from this License).
-
-10.4. Distributing Source Code Form that is Incompatible With Secondary
-Licenses
-
-If You choose to distribute Source Code Form that is Incompatible With
-Secondary Licenses under the terms of this version of the License, the
-notice described in Exhibit B of this License must be attached.
-
-Exhibit A - Source Code Form License Notice
--------------------------------------------
-
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-If it is not possible or desirable to put the notice in a particular
-file, then You may include the notice in a location (such as a LICENSE
-file in a relevant directory) where a recipient would be likely to look
-for such a notice.
-
-You may add additional accurate notices of copyright ownership.
-
-Exhibit B - "Incompatible With Secondary Licenses" Notice
----------------------------------------------------------
-
- This Source Code Form is "Incompatible With Secondary Licenses", as
- defined by the Mozilla Public License, v. 2.0.
diff --git a/3rdparty-notices/Kinect-V2-SDK-Eula.rtf b/3rdparty-notices/Kinect-V2-SDK-Eula.rtf
new file mode 100644
index 00000000..c2e634f7
--- /dev/null
+++ b/3rdparty-notices/Kinect-V2-SDK-Eula.rtf
@@ -0,0 +1,525 @@
+{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff39\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi0\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;}
+{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;}{\f3\fbidi \froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f10\fbidi \fnil\fcharset2\fprq2{\*\panose 05000000000000000000}Wingdings;}
+{\f34\fbidi \froman\fcharset1\fprq2{\*\panose 02040503050406030204}Cambria Math;}{\f38\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0502040204020203}Segoe UI;}{\f39\fbidi \fswiss\fcharset0\fprq2{\*\panose 00000000000000000000}Tahoma;}
+{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
+{\fhimajor\f31502\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0302020204030204}Calibri Light;}{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
+{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
+{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f40\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
+{\f41\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f43\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f44\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f45\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
+{\f46\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f47\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f48\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f50\fbidi \fswiss\fcharset238\fprq2 Arial CE;}
+{\f51\fbidi \fswiss\fcharset204\fprq2 Arial Cyr;}{\f53\fbidi \fswiss\fcharset161\fprq2 Arial Greek;}{\f54\fbidi \fswiss\fcharset162\fprq2 Arial Tur;}{\f55\fbidi \fswiss\fcharset177\fprq2 Arial (Hebrew);}
+{\f56\fbidi \fswiss\fcharset178\fprq2 Arial (Arabic);}{\f57\fbidi \fswiss\fcharset186\fprq2 Arial Baltic;}{\f58\fbidi \fswiss\fcharset163\fprq2 Arial (Vietnamese);}{\f60\fbidi \fmodern\fcharset238\fprq1 Courier New CE;}
+{\f61\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;}{\f63\fbidi \fmodern\fcharset161\fprq1 Courier New Greek;}{\f64\fbidi \fmodern\fcharset162\fprq1 Courier New Tur;}{\f65\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew);}
+{\f66\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic);}{\f67\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f68\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);}{\f430\fbidi \fswiss\fcharset238\fprq2 Tahoma CE;}
+{\f431\fbidi \fswiss\fcharset204\fprq2 Tahoma Cyr;}{\f433\fbidi \fswiss\fcharset161\fprq2 Tahoma Greek;}{\f434\fbidi \fswiss\fcharset162\fprq2 Tahoma Tur;}{\f435\fbidi \fswiss\fcharset177\fprq2 Tahoma (Hebrew);}
+{\f436\fbidi \fswiss\fcharset178\fprq2 Tahoma (Arabic);}{\f437\fbidi \fswiss\fcharset186\fprq2 Tahoma Baltic;}{\f438\fbidi \fswiss\fcharset163\fprq2 Tahoma (Vietnamese);}{\f439\fbidi \fswiss\fcharset222\fprq2 Tahoma (Thai);}
+{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
+{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
+{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
+{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
+{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
+{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31528\fbidi \fswiss\fcharset238\fprq2 Calibri Light CE;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}
+{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}
+{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
+{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
+{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
+{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
+{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
+{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
+{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
+{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
+{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}
+{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}
+{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
+{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
+{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}
+{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;
+\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\caccentone\ctint255\cshade191\red46\green116\blue181;\caccentone\ctint255\cshade127\red31\green77\blue120;\red31\green73\blue125;}
+{\*\defchp \fs22\loch\af31506\hich\af31506\dbch\af31505 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{
+\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\loch\f39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{
+\s1\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\outlinelevel0\rin0\lin0\itap0 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\loch\f39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink15 \sqformat
+heading 1;}{\s2\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\loch\f39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033
+\sbasedon0 \snext0 \slink16 \sqformat heading 2;}{\s3\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\outlinelevel2\rin0\lin0\itap0 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0
+\fs24\lang1033\langfe1033\loch\f39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink17 \sqformat heading 3;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
+\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1
+\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe1033\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused
+Normal Table;}{\*\cs15 \additive \rtlch\fcs1 \af0\afs32 \ltrch\fcs0 \fs32\cf17\loch\f31502\hich\af31502\dbch\af31501 \sbasedon10 \slink1 \slocked \spriority9 Heading 1 Char;}{\*\cs16 \additive \rtlch\fcs1 \af0\afs26 \ltrch\fcs0
+\fs26\cf17\loch\f31502\hich\af31502\dbch\af31501 \sbasedon10 \slink2 \slocked \ssemihidden \spriority9 Heading 2 Char;}{\*\cs17 \additive \rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs24\cf18\loch\f31502\hich\af31502\dbch\af31501
+\sbasedon10 \slink3 \slocked \ssemihidden \spriority9 Heading 3 Char;}{\s18\ql \li0\ri0\nowidctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0
+\fs24\lang1033\langfe1033\loch\f39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext18 \slink19 \sunhideused \styrsid1930808 header;}{\*\cs19 \additive \rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24
+\sbasedon10 \slink18 \slocked \styrsid1930808 Header Char;}{\s20\ql \li0\ri0\nowidctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0
+\fs24\lang1033\langfe1033\loch\f39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext20 \slink21 \sunhideused \styrsid1930808 footer;}{\*\cs21 \additive \rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24
+\sbasedon10 \slink20 \slocked \styrsid1930808 Footer Char;}{\*\cs22 \additive \rtlch\fcs1 \af0\afs16 \ltrch\fcs0 \fs16 \sbasedon10 \ssemihidden \sunhideused \styrsid4929922 annotation reference;}{
+\s23\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af39\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\loch\f39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033
+\sbasedon0 \snext23 \slink24 \ssemihidden \sunhideused \styrsid4929922 annotation text;}{\*\cs24 \additive \rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20 \sbasedon10 \slink23 \slocked \ssemihidden \styrsid4929922 Comment Text Char;}{
+\s25\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \ab\af39\afs20\alang1025 \ltrch\fcs0 \b\fs20\lang1033\langfe1033\loch\f39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033
+\sbasedon23 \snext23 \slink26 \ssemihidden \sunhideused \styrsid4929922 annotation subject;}{\*\cs26 \additive \rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\f39\fs20 \sbasedon24 \slink25 \slocked \ssemihidden \styrsid4929922 Comment Subject Char;}{
+\s27\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af38\afs18\alang1025 \ltrch\fcs0 \fs18\lang1033\langfe1033\loch\f38\hich\af38\dbch\af31505\cgrid\langnp1033\langfenp1033
+\sbasedon0 \snext27 \slink28 \ssemihidden \sunhideused \styrsid4929922 Balloon Text;}{\*\cs28 \additive \rtlch\fcs1 \af38\afs18 \ltrch\fcs0 \f38\fs18 \sbasedon10 \slink27 \slocked \ssemihidden \styrsid4929922 Balloon Text Char;}{
+\s29\ql \li720\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin720\itap0\contextualspace \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\loch\f39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033
+\sbasedon0 \snext29 \sqformat \spriority34 \styrsid5338358 List Paragraph;}}{\*\listtable{\list\listtemplateid-1933036156\listsimple{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat0\levelspace0\levelindent0{\leveltext
+\'01*;}{\levelnumbers;}}{\listname ;}\listid-2}{\list\listtemplateid1663739596\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693
+\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0
+\fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li2880\lin2880 }{\listlevel
+\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0
+\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1
+\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0
+\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
+\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li6480\lin6480 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693
+\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li7200\lin7200 }{\listname ;}\listid296491462}{\list\listtemplateid1166995046\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext
+\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691
+\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0
+\fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li3600\lin3600 }{\listlevel
+\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0
+\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1
+\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0
+\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li6480\lin6480 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
+\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li7200\lin7200 }{\listname ;}\listid728115947}{\list\listtemplateid-576179798\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0
+\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li1260\lin1260 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
+\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li1980\lin1980 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693
+\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li2700\lin2700 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}
+\f3\fbias0 \fi-360\li3420\lin3420 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li4140\lin4140 }
+{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li4860\lin4860 }{\listlevel\levelnfc23\levelnfcn23
+\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li5580\lin5580 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0
+\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li6300\lin6300 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0
+\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li7020\lin7020 }{\listname ;}\listid844515658}{\list\listtemplateid2023228974\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0
+\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li1800\lin1800 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0
+\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li2520\lin2520 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
+\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li3240\lin3240 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689
+\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li3960\lin3960 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0
+\fi-360\li4680\lin4680 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li5400\lin5400 }{\listlevel
+\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li6120\lin6120 }{\listlevel\levelnfc23\levelnfcn23\leveljc0
+\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li6840\lin6840 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1
+\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li7560\lin7560 }{\listname ;}\listid1411999391}{\list\listtemplateid-926094602\listhybrid{\listlevel\levelnfc23\levelnfcn23
+\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li1800\lin1800 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1
+\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li2520\lin2520 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0
+{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li3240\lin3240 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689
+\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li3960\lin3960 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0
+\fi-360\li4680\lin4680 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li5400\lin5400 }{\listlevel
+\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li6120\lin6120 }{\listlevel\levelnfc23\levelnfcn23\leveljc0
+\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li6840\lin6840 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1
+\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li7560\lin7560 }{\listname ;}\listid1653637060}{\list\listtemplateid11189262\listsimple{\listlevel\levelnfc4\levelnfcn4\leveljc0
+\leveljcn0\levelfollow0\levelstartat1\levelold\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af39 \ltrch\fcs0 \b\f39\fbias0 }{\listname ;}\listid1681809293}}{\*\listoverridetable{\listoverride\listid1681809293
+\listoverridecount0\ls1}{\listoverride\listid296491462\listoverridecount0\ls2}{\listoverride\listid-2\listoverridecount1{\lfolevel\listoverrideformat{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat0\levelold\levelspace0
+\levelindent360{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 }}\ls3}{\listoverride\listid1653637060\listoverridecount0\ls4}{\listoverride\listid1411999391\listoverridecount0\ls5}{\listoverride\listid728115947\listoverridecount0\ls6}
+{\listoverride\listid844515658\listoverridecount0\ls7}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}{\*\rsidtbl \rsid73750\rsid91414\rsid675470\rsid1909739\rsid1930808\rsid2042763\rsid3882049\rsid3949644\rsid4196885\rsid4796860\rsid4929922\rsid5338358
+\rsid6816407\rsid6969745\rsid7096959\rsid7297413\rsid7679777\rsid7695099\rsid8139599\rsid8222967\rsid8745415\rsid9392452\rsid9909308\rsid10056964\rsid10234499\rsid10296048\rsid10437656\rsid10570424\rsid10897255\rsid11022658\rsid11090850\rsid11808396
+\rsid11829074\rsid11871500\rsid11937711\rsid11949759\rsid13329978\rsid13781786\rsid14225958\rsid14551182\rsid14579304\rsid15495172\rsid15621282\rsid16385968\rsid16457108}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0
+\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\creatim\yr2014\mo8\dy27\hr11\min25}{\revtim\yr2014\mo8\dy27\hr11\min25}{\version1}{\edmins0}{\nofpages6}{\nofwords1938}{\nofchars11052}{\nofcharsws12965}{\vern57437}}{\*\xmlnstbl {\xmlns1 http://schemas.
+microsoft.com/office/word/2003/wordml}}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect
+\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont0\relyonvml1\donotembedlingdata0\grfdocevents0\validatexml0\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors0\horzdoc\dghspace120\dgvspace120\dghorigin1701
+\dgvorigin1984\dghshow0\dgvshow3\jcompress\viewscale100\splytwnine\ftnlytwnine\htmautsp\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\rempersonalinfo\allowfieldendsel\wrppunct\asianbrkrule\rsidroot6816407
+\newtblstyruls\nogrowautofit\remdttm\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0{\*\wgrffmtfilter 2450}
+\nofeaturethrottle1\ilfomacatclnup0{\*\ftnsep \ltrpar \pard\plain \ltrpar\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0\pararsid1930808 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0
+\fs24\lang1033\langfe1033\loch\af39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af39 \ltrch\fcs0 \insrsid10296048 \chftnsep
+\par }}{\*\ftnsepc \ltrpar \pard\plain \ltrpar\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0\pararsid1930808 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\loch\af39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 {
+\rtlch\fcs1 \af39 \ltrch\fcs0 \insrsid10296048 \chftnsepc
+\par }}{\*\aftnsep \ltrpar \pard\plain \ltrpar\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0\pararsid1930808 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\loch\af39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 {
+\rtlch\fcs1 \af39 \ltrch\fcs0 \insrsid10296048 \chftnsep
+\par }}{\*\aftnsepc \ltrpar \pard\plain \ltrpar\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0\pararsid1930808 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\loch\af39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 {
+\rtlch\fcs1 \af39 \ltrch\fcs0 \insrsid10296048 \chftnsepc
+\par }}\ltrpar \sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4
+\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}
+{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sb120\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1
+\af39\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\loch\af39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Microsoft Kinect for Windows}{
+\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 v2}{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Software Development Kit (SDK)
+\par }\pard \ltrpar\ql \li0\ri0\sa120\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \li0\ri0\sb120\sa120\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+These license terms are an agreement between Microsoft Corporation (or based on where you live, one of its affiliates) and you. Please read them. They apply to the software named above, \hich\af39\dbch\af31505\loch\f39
+which includes the media on which you received it, if any. It also applies to any Microsoft}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \fi-360\li360\ri0\sb120\sa120\nowidctlpar\tx360\wrapdefault\faauto\rin0\lin360\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0
+\f1\fs20\insrsid8745415 \tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 updates}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415 ,
+\par }\pard \ltrpar\ql \fi-360\li360\ri0\sb120\sa120\nowidctlpar\wrapdefault\faauto\rin0\lin360\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415
+\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 supplements,
+\par }{\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415 \tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+documentation, and}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415
+\par }{\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415 \tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+support services}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \li0\ri0\sb120\sa120\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+for this software, unless other terms accompany those items. If so, those terms apply.
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 The softwar\hich\af39\dbch\af31505\loch\f39 e is licensed, not sold.}{\rtlch\fcs1 \af39 \ltrch\fcs0 \insrsid8745415 \hich\af39\dbch\af31505\loch\f39 }{
+\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 \hich\f39
+By downloading, installing, accessing, or using the software, you accept all terms in this agreement. If you do not accept them, do not download, install, access, or use the software. \'93\loch\f39 \hich\f39 You\'94\loch\f39 \hich\f39 or \'93\loch\f39
+\hich\f39 you\'94\loch\f39 means the individual who downloa\hich\af39\dbch\af31505\loch\f39
+ds, installs, accesses, or uses the software (and, if you represent a legal entity, it also means that entity, and you represent and warrant that you are authorized to enter into this agreement for that entity).}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 If you comply with these license terms, you\hich\af39\dbch\af31505\loch\f39 have the rights below.}{\rtlch\fcs1 \ab\af1\afs20 \ltrch\fcs0
+\b\f1\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \li0\ri0\sb200\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 {\rtlch\fcs1 \ab\af1\afs20 \ltrch\fcs0 \b\f1\fs20\insrsid8745415 \hich\af1\dbch\af31505\loch\f1 1. }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 INSTALLATION AND USE RIGHTS.
+\par {\pntext\pard\plain\ltrpar \rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\f39\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 a.\tab}}\pard \ltrpar\ql \fi-270\li450\ri0\sa100\nowidctlpar\tx540\wrapdefault{\*\pn \pnlvlbody\ilvl0\ls1\pnrnot0\pnlcltr\pnb1
+\pnf39\pnstart1 {\pntxta .}}\faauto\ls1\rin0\lin450\itap0\pararsid6816407 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Installation and Use.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 You may (i) install and use any number of copies of the software (only when installed using the accompanying software installer package) on your computer to design, develop, and \hich\af39\dbch\af31505\loch\f39
+test your programs that run specifically on a Microsoft Windows operating system, and that are intended for use solely in connection with }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid11871500 \hich\af39\dbch\af31505\loch\f39 the }{\rtlch\fcs1
+\af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Microsoft Kinect for Windows}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid3882049 \hich\af39\dbch\af31505\loch\f39 v}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 2}{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 }{
+\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 sensor}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 \hich\f39 (\'93\loch\f39 Kinect }{\rtlch\fcs1 \af39\afs20
+\ltrch\fcs0 \fs20\insrsid3882049 \hich\af39\dbch\af31505\loch\f39 v}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid7679777 \hich\af39\dbch\af31505\loch\f39
+ Sensor}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39 )}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+, and its associated drivers and runtime software, and no other sensor }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid73750 \hich\af39\dbch\af31505\loch\f39 unless such sensor is supported by Microsoft }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 (}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid6969745 \hich\af39\dbch\af31505\loch\f39 collectively, the }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\loch\af39\dbch\af31505\hich\f39 \'93\loch\f39 Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 V2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 \hich\f39
+ Applications\'94\loch\f39 ), and (ii) distribute your Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3882049 \hich\af39\dbch\af31505\loch\f39 v}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 2 }{
+\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Applications, subject to the terms in this agreement.
+\par {\pntext\pard\plain\ltrpar \rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \b\f39\fs20\insrsid7695099 \hich\af39\dbch\af31505\loch\f39 b.\tab}}\pard \ltrpar\ql \fi-270\li450\ri0\sa100\nowidctlpar\tx540\wrapdefault{\*\pn \pnlvlbody\ilvl0\ls1\pnrnot0\pnlcltr\pnb1
+\pnf39\pnstart1 {\pntxta .}}\faauto\ls1\rin0\lin450\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \b\fs20\insrsid7695099 \hich\af39\dbch\af31505\loch\f39 Tele\hich\af39\dbch\af31505\loch\f39 metry Data Collection. }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid7695099 \hich\af39\dbch\af31505\loch\f39 When in use by you, the software will provide Microsoft with telemetry data }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\lang9\langfe1033\langnp9\insrsid7695099 \hich\af39\dbch\af31505\loch\f39
+(e.g. operating system, number of processors, graphic chipset, memory, device type, locale, time) regarding your installation and use. }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\lang9\langfe1033\langnp9\insrsid14225958 \hich\af39\dbch\af31505\loch\f39
+The data will not be u\hich\af39\dbch\af31505\loch\f39 sed to identify specific individuals. }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\lang9\langfe1033\langnp9\insrsid7695099 \hich\af39\dbch\af31505\loch\f39 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\lang9\langfe1033\langnp9\insrsid8222967 \hich\af39\dbch\af31505\loch\f39 Microsoft will}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\lang9\langfe1033\langnp9\insrsid7695099 \hich\af39\dbch\af31505\loch\f39 use the Telemetry Data for product and }{
+\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\lang9\langfe1033\langnp9\insrsid91414 \hich\af39\dbch\af31505\loch\f39 service}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\lang9\langfe1033\langnp9\insrsid7695099 \hich\af39\dbch\af31505\loch\f39 improvements.
+}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid7695099\charrsid7695099
+\par {\pntext\pard\plain\ltrpar \rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\f39\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 c.\tab}}\pard \ltrpar\ql \fi-270\li450\ri0\sa100\nowidctlpar\tx540\wrapdefault{\*\pn \pnlvlbody\ilvl0\ls1\pnrnot0\pnlcltr\pnb1
+\pnf39\pnstart1 {\pntxta .}}\faauto\ls1\rin0\lin450\itap0\pararsid6816407 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Included Microsoft Programs.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 The software includes other Microsoft programs. The license terms with those programs apply to your use o\hich\af39\dbch\af31505\loch\f39 f them.
+\par {\pntext\pard\plain\ltrpar \rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\f39\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 d.\tab}}\pard \ltrpar\ql \fi-270\li450\ri0\sa100\nowidctlpar\tx540\wrapdefault{\*\pn \pnlvlbody\ilvl0\ls1\pnrnot0\pnlcltr\pnb1
+\pnf39\pnstart1 {\pntxta .}}\faauto\ls1\rin0\lin450\itap0\pararsid6816407 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 No High Risk Use. WARNING:}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 The Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 v}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 2 }{
+\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid7679777 \hich\af39\dbch\af31505\loch\f39 S}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 ensor and the software are not fault-tolerant. The Kinect }{\rtlch\fcs1
+\af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 v}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid7679777
+\hich\af39\dbch\af31505\loch\f39 Sensor }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 and the software are not designed or intended for use with any program where failure or fault of any kind of the Kinect }{
+\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 v}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid7679777
+\hich\af39\dbch\af31505\loch\f39 Sensor}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 or soft
+\hich\af39\dbch\af31505\loch\f39 \hich\f39 ware could lead to death or serious bodily injury of any person, or to severe physical or environmental damage (\'93\loch\f39 \hich\f39 High Risk Use\'94\loch\f39
+). You are not licensed to, and you agree not to, use, distribute or sublicense the use of the Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid11871500 \hich\af39\dbch\af31505\loch\f39 v2 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Sensor and/or software in, or in conjunction with, High Risk Use. High Risk Use is STRICTLY PROHIBITED. }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 High Risk Use includes, for example, the following: aircraft navigation and control of other modes of human mass transportation, nuclear or chemical f\hich\af39\dbch\af31505\loch\f39 acilities. }{\rtlch\fcs1 \ab\af1\afs20
+\ltrch\fcs0 \b\f1\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \li0\ri0\sb200\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 {\rtlch\fcs1 \ab\af1\afs20 \ltrch\fcs0 \b\f1\fs20\insrsid8745415 \hich\af1\dbch\af31505\loch\f1 2}{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 . ADDITIONAL LICENSING REQUIREMENTS AND/OR USE RIGHTS
+\par }\pard \ltrpar\ql \fi-183\li363\ri0\sa100\nowidctlpar\tx363\wrapdefault\faauto\rin0\lin363\itap0 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 a.\tab Distributable Code}{\rtlch\fcs1 \af39\afs20
+\ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 . The software contains code that you are permitted to distribute solely in Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 v}{
+\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Applications if you comply with the terms below.
+\par }\pard\plain \ltrpar\s3\ql \fi-357\li1077\ri0\sb120\sa120\nowidctlpar\tx1080\wrapdefault\faauto\outlinelevel2\rin0\lin1077\itap0 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0
+\fs24\lang1033\langfe1033\loch\af39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 i.\tab Right to Use and \hich\af39\dbch\af31505\loch\f39 \hich\f39
+Distribute. The code and text files listed below are \'93\loch\f39 \hich\f39 Distributable Code.\'94
+\par }\pard\plain \ltrpar\ql \fi-358\li1435\ri0\sb120\sa120\nowidctlpar\tx1437\wrapdefault\faauto\rin0\lin1435\itap0 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\loch\af39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 {
+\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\ul\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 REDIST.TXT Files}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 . You may copy and distribute the object code form of code listed in REDIST.TXT files}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid11871500 \hich\af39\dbch\af31505\loch\f39
+ as part of your Kinect v2 Application}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 .
+\par }\pard \ltrpar\ql \fi-358\li1435\ri0\sb120\sa120\nowidctlpar\wrapdefault\faauto\rin0\lin1435\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\ul\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Sample Code}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 . You may modify, copy \hich\af39\dbch\af31505\loch\f39
+and distribute the source and object code form of code in the Samples subdirectory}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid11871500 \hich\af39\dbch\af31505\loch\f39 as part of your Kinect v2 Application}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0
+\f1\fs20\insrsid8745415 .}{\rtlch\fcs1 \af0\afs20 \ltrch\fcs0 \f0\fs20\insrsid8745415
+\par }{\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\ul\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Third Party Distribution}{\rtlch\fcs1 \af39\afs20
+\ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 . You may permit distributors of your Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 v2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Applications to copy and distribute the Distributable Code a\hich\af39\dbch\af31505\loch\f39 s part of those Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644
+\hich\af39\dbch\af31505\loch\f39 v}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid4929922 \hich\af39\dbch\af31505\loch\f39 2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Applications.
+\par {\listtext\pard\plain\ltrpar \s29 \rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f3\fs20\insrsid5338358\charrsid11829074 \loch\af3\dbch\af31505\hich\f3 \'b7\tab}}\pard\plain \ltrpar\s29\ql \fi-360\li1440\ri0\nowidctlpar
+\tx900\wrapdefault\faauto\ls7\rin0\lin1440\itap0\pararsid11829074\contextualspace \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\loch\af39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\ul\insrsid5338358\charrsid11829074 \hich\af39\dbch\af31505\loch\f39 Third Party Programs}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid5338358\charrsid11829074 \hich\af39\dbch\af31505\loch\f39
+. This software may contain certain third-party programs. You agree that your use of them is governed by the license terms provided with those programs.
+\par }\pard\plain \ltrpar\ql \li0\ri0\sa100\nowidctlpar\tx1512\wrapdefault\faauto\rin0\lin0\itap0\pararsid11829074 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\loch\af39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 {
+\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \li720\ri0\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin720\itap0 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 ii. Distribution Requirements.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 For any D\hich\af39\dbch\af31505\loch\f39 istributable Code you distribute, you must: }{\rtlch\fcs1
+\af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \fi-360\li1440\ri0\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin1440\itap0 {\rtlch\fcs1 \af10\afs20 \ltrch\fcs0 \f10\fs20\insrsid8745415 \loch\af10\dbch\af31505\hich\f10 \'a7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 add significant primary functionality to it in your Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid11871500 \hich\af39\dbch\af31505\loch\f39 v2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Applications;
+\par }\pard \ltrpar\ql \fi-360\li1440\ri0\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin1440\itap0\pararsid11829074 {\rtlch\fcs1 \af10\afs20 \ltrch\fcs0 \f10\fs20\insrsid8745415 \loch\af10\dbch\af31505\hich\f10 \'a7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 distribute Distributable Code included in a setup program only as part of that setup program without modification}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid10234499
+\hich\af39\dbch\af31505\loch\f39 ;}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid11808396
+\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f10\fs20\insrsid14225958 \loch\af10\dbch\af31505\hich\f10 \'a7\tab}}\pard \ltrpar\ql \fi-360\li1440\ri0\sa100\nowidctlpar\wrapdefault\faauto\ls6\rin0\lin1440\itap0\pararsid11829074 {
+\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid14225958 \hich\af39\dbch\af31505\loch\f39 c}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid13329978 \hich\af39\dbch\af31505\loch\f39 learly state in your Kinect V2 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 A}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid13329978 \hich\af39\dbch\af31505\loch\f39 pplication (as well as require your licensees to do so) }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid15495172 \hich\af39\dbch\af31505\loch\f39 a privacy statement}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid13329978 \hich\af39\dbch\af31505\loch\f39
+ regarding the collection and use of customer data as well as the following statement regarding Microsoft\hich\f39 \rquote \loch\f39 s }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid91414 \hich\af39\dbch\af31505\loch\f39 collection and }{\rtlch\fcs1
+\af39\afs20 \ltrch\fcs0 \fs20\insrsid13329978 \hich\af39\dbch\af31505\loch\f39 use of customer data}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid10234499 \hich\af39\dbch\af31505\loch\f39 \hich\f39 : \'93}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\ul\insrsid10234499\charrsid11829074 \hich\af39\dbch\af31505\loch\f39 No\hich\af39\dbch\af31505\loch\f39 te}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid10234499 \hich\af39\dbch\af31505\loch\f39 : When using }{\rtlch\fcs1 \af39\afs20
+\ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 the _________________(insert name of Your}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid10234499 \hich\af39\dbch\af31505\loch\f39 Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 v}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid10234499 \hich\af39\dbch\af31505\loch\f39 2 Application}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 )}
+{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid11871500 \hich\af39\dbch\af31505\loch\f39 with a Kinect for Windows v2 Sensor}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid14225958 ,}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid10234499
+\hich\af39\dbch\af31505\loch\f39 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\lang9\langfe1033\langnp9\insrsid10234499 \hich\af39\dbch\af31505\loch\f39
+Microsoft will collect telemetry data (e.g. operating system, number of processors, graphic chipset, memory, device type, locale, time) in\hich\af39\dbch\af31505\loch\f39 order to improve }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\lang9\langfe1033\langnp9\insrsid91414 \hich\af39\dbch\af31505\loch\f39 Microsoft products and services}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\lang9\langfe1033\langnp9\insrsid10234499 \hich\af39\dbch\af31505\loch\f39 \hich\f39
+. The data will not be used to identify specific individuals.\'94\loch\f39 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid13329978\charrsid11871500 \hich\af39\dbch\af31505\loch\f39
+\par }\pard \ltrpar\ql \fi-360\li1440\ri0\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin1440\itap0 {\rtlch\fcs1 \af10\afs20 \ltrch\fcs0 \f10\fs20\insrsid8745415 \loch\af10\dbch\af31505\hich\f10 \'a7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 clearly state in marketing materials, documentation and other materials related to the Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 v}{\rtlch\fcs1
+\af39\afs20 \ltrch\fcs0 \fs20\insrsid13329978 \hich\af39\dbch\af31505\loch\f39 2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Application (e.g. on the webpages on whi\hich\af39\dbch\af31505\loch\f39
+ch the Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid13329978 \hich\af39\dbch\af31505\loch\f39 V2 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Application is described or from which the Kinect }{
+\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid13329978 \hich\af39\dbch\af31505\loch\f39 V2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+ Application may be downloaded or otherwise obtained), that it is intended for use only with the Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid13329978 \hich\af39\dbch\af31505\loch\f39 V2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid16457108 \hich\af39\dbch\af31505\loch\f39 S}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 ensor;
+\par }\pard \ltrpar\ql \fi-360\li1440\ri0\sb120\sa120\nowidctlpar\wrapdefault\faauto\rin0\lin1440\itap0 {\rtlch\fcs1 \af10\afs20 \ltrch\fcs0 \f10\fs20\insrsid8745415 \loch\af10\dbch\af31505\hich\f10 \'a7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 require }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 licensees, }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 distributors and external end users to agree to }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 license }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 terms that}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 are as protective of Microsoft\hich\f39 \rquote \loch\f39
+s rights and interests in the Distributable Code as stated in this agreement; and }{\rtlch\fcs1 \af0\afs20 \ltrch\fcs0 \f0\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \fi-360\li1440\ri0\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin1440\itap0 {\rtlch\fcs1 \af10\afs20 \ltrch\fcs0 \f10\fs20\insrsid8745415 \loch\af10\dbch\af31505\hich\f10 \'a7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 display your valid copyright notice on your Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 v}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid13329978 \hich\af39\dbch\af31505\loch\f39 2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Applications}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415 .
+\par }\pard\plain \ltrpar\s3\ql \fi-357\li1077\ri0\sb120\sa120\nowidctlpar\tx1077\tx1440\wrapdefault\faauto\outlinelevel2\rin0\lin1077\itap0 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0
+\fs24\lang1033\langfe1033\loch\af39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 iii.\tab Distribution Restrictions. You may not:
+\par }\pard\plain \ltrpar\ql \fi-358\li1435\ri0\sb120\sa120\nowidctlpar\tx1437\wrapdefault\faauto\rin0\lin1435\itap0 \rtlch\fcs1 \af39\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\loch\af39\hich\af39\dbch\af31505\cgrid\langnp1033\langfenp1033 {
+\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 alter\hich\af39\dbch\af31505\loch\f39
+ any copyright, trademark or patent notice in the Distributable Code;
+\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f10\fs20\insrsid13329978 \loch\af10\dbch\af31505\hich\f10 \'a7\tab}}\pard \ltrpar\ql \fi-360\li1440\ri0\sb120\sa120\nowidctlpar
+\tx1437\wrapdefault\faauto\ls2\rin0\lin1440\itap0\pararsid11829074 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid13329978 \hich\af39\dbch\af31505\loch\f39
+License the Microsoft Distributable Code in a manner inconsistent with the terms of this License.
+\par }\pard \ltrpar\ql \fi-358\li1435\ri0\sb120\sa120\nowidctlpar\tx1437\wrapdefault\faauto\rin0\lin1435\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 use Microsoft\hich\f39 \rquote \loch\f39 s trademarks, including but not limited to Microsoft, Kinect and Window\hich\af39\dbch\af31505\loch\f39 s, in your Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 v2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Applications\hich\f39 \rquote \loch\f39 names or in a way that suggests your Kinect for }{\rtlch\fcs1
+\af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 v2 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Applications come from or are endorsed by Microsoft;
+\par }\pard \ltrpar\ql \fi-358\li1435\ri0\sb120\sa120\nowidctlpar\wrapdefault\faauto\rin0\lin1435\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 distribute Distributable Code to run on a platform other than a Microsoft Windows operating system;
+\par }\pard \ltrpar\ql \fi-360\li1437\ri0\sb120\sa120\nowidctlpar\wrapdefault\faauto\rin0\lin1437\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 include Distr\hich\af39\dbch\af31505\loch\f39 ibutable Code in malicious, obscene, deceptive or unlawful programs;
+\par }\pard \ltrpar\ql \fi-358\li1435\ri0\sb120\sa120\nowidctlpar\tx1437\wrapdefault\faauto\rin0\lin1435\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 include Distributable Code for any programs designed or intended for High Risk Use; or}{\rtlch\fcs1 \af0\afs20 \ltrch\fcs0 \f0\fs20\insrsid8745415
+\par }{\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+modify or distribute the source code of any Distributable Code so that any part of it becomes su\hich\af39\dbch\af31505\loch\f39
+bject to an Excluded License. An Excluded License is one that requires, as a condition of use, modification or distribution, that}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \fi-357\li1792\ri0\sb120\sa120\nowidctlpar\tx1795\wrapdefault\faauto\rin0\lin1792\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 the code be disclosed or distributed in source code form; or
+\par }\pard \ltrpar\ql \fi-357\li1792\ri0\sb120\sa120\nowidctlpar\wrapdefault\faauto\rin0\lin1792\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 others have the right to modify it.
+\par }\pard \ltrpar\ql \li0\ri0\sb200\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 {\rtlch\fcs1 \ab\af1\afs20 \ltrch\fcs0 \b\f1\fs20\insrsid8745415 \hich\af1\dbch\af31505\loch\f1 3. }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 SCOPE OF LICENSE.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+ The software is licensed, not sold. This agreement only gives you some rights to use the software. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the software only as expressly permit
+\hich\af39\dbch\af31505\loch\f39 t\hich\af39\dbch\af31505\loch\f39 ed in this agreement. In doing so, you must comply with any technical limitations in the software that only allow you to use it in certain ways. You may not:}{\rtlch\fcs1 \af1\afs20
+\ltrch\fcs0 \f1\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \fi-360\li720\ri0\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin720\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 access or use, or attempt to access or use, features of the Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 v2 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 Sensor that are not \hich\af39\dbch\af31505\loch\f39 exposed or enabled by the software;
+\par }{\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415\charrsid1909739 \hich\af39\dbch\af31505\loch\f39 distribute Kinect }{\rtlch\fcs1
+\af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644\charrsid1909739 \hich\af39\dbch\af31505\loch\f39 v2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415\charrsid1909739 \hich\af39\dbch\af31505\loch\f39 Applications for use with any sensor other than }{
+\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid11871500\charrsid1909739 \hich\af39\dbch\af31505\loch\f39 the }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415\charrsid1909739 \hich\af39\dbch\af31505\loch\f39 Kinect }{\rtlch\fcs1 \af39\afs20
+\ltrch\fcs0 \fs20\insrsid3949644\charrsid1909739 \hich\af39\dbch\af31505\loch\f39 v2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid16457108\charrsid1909739 \hich\af39\dbch\af31505\loch\f39 Sensor}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid14225958 \hich\af39\dbch\af31505\loch\f39 or }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415\charrsid1909739 \hich\af39\dbch\af31505\loch\f39 its associated drivers and runtime software;}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 }{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \fi-360\li720\ri0\sb200\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin720\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 use the software or any Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644 \hich\af39\dbch\af31505\loch\f39 v2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 Applications in any High Risk Use; }{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \fi-360\li720\ri0\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin720\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 work \hich\af39\dbch\af31505\loch\f39 around any technical limitations in the software;
+\par }{\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+reverse engineer, decompile, or disassemble any part of the software not provided in source code form, except and only to the extent that applicable law expressly permits, despite this limitation;
+\par }\pard \ltrpar\ql \fi-360\li720\ri0\sb200\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin720\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 publi\hich\af39\dbch\af31505\loch\f39 sh the software for others to copy;
+\par }\pard \ltrpar\ql \fi-360\li720\ri0\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin720\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 rent, lease, or lend the software;
+\par }\pard \ltrpar\ql \fi-360\li720\ri0\sb200\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin720\itap0 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 transfer the software or this agreement to any third party; or
+\par }\pard \ltrpar\ql \fi-360\li720\ri0\sb200\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin720\itap0\pararsid11829074 {\rtlch\fcs1 \af3\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid8745415 \loch\af3\dbch\af31505\hich\f3 \'b7\tab }{\rtlch\fcs1 \af39\afs20
+\ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 use the software for commercial software hosting services.}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\insrsid8745415
+\par }\pard \ltrpar\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 4. REGULATORY COMPLIANCE. }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 You agree that your developmen\hich\af39\dbch\af31505\loch\f39 t, marketing, sales, and distribution of Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid3949644
+\hich\af39\dbch\af31505\loch\f39 v2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+ Applications shall be in compliance with all applicable legal requirements, including compliance with the medical device regulatory requirements of the U.S. Federal Food, Drug, and Cosmetic Act and any ass\hich\af39\dbch\af31505\loch\f39
+ociated requirements, or similar laws, regulations, or policies in other countries or territories.\~
+ To the extent required by law, you are solely responsible for obtaining or filing any approval, clearance, registration, permit, or other regulatory author\hich\af39\dbch\af31505\loch\f39 i\hich\af39\dbch\af31505\loch\f39
+zation and shall comply with the requirements of such authorization.
+\par }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\cf19\insrsid8745415
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 5. ACKNOWLEDGEMENT AND WAIVER. }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+You acknowledge the software may allow you to control the Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid16457108 \hich\af39\dbch\af31505\loch\f39 v2 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+Sensor, which are mechanical hardware devices that include motors to move the device, \hich\af39\dbch\af31505\loch\f39
+a fan to cool it, and other mechanical components. Depending on how you elect to use the software, you could harm persons or damage or destroy the Kinect}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid2042763 \hich\af39\dbch\af31505\loch\f39 v2}{
+\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Sensor, products incorporating the Kinect}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid2042763 \hich\af39\dbch\af31505\loch\f39 v2}{\rtlch\fcs1 \af39\afs20
+\ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Sensor, or other property. In using the software, you must take steps to design and test your Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid2042763
+\hich\af39\dbch\af31505\loch\f39 v2 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+Applications to ensure that your applications do not present unreasonable risks of personal injury or death, property damage, or other losses. Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid2042763 \hich\af39\dbch\af31505\loch\f39 v
+\hich\af39\dbch\af31505\loch\f39 2 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+Sensors utilize complex hardware and software technology that may not always function as intended. You must design your application so that any failure of a Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid2042763
+\hich\af39\dbch\af31505\loch\f39 v2 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Sensor and/or the software does not cause personal injury or death, property damage, or\hich\af39\dbch\af31505\loch\f39
+ other losses. If you choose to use the software, you assume all risk that your use of the Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid2042763 \hich\af39\dbch\af31505\loch\f39 v2 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 Sensors and/or the software causes any harm or loss, including to the end users of your Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid2042763 \hich\af39\dbch\af31505\loch\f39 v2}{\rtlch\fcs1 \af39\afs20
+\ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Applications, and you agree to waive all claims against M\hich\af39\dbch\af31505\loch\f39
+icrosoft and its affiliates related to such use (including but not limited to any claim that a Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid2042763 \hich\af39\dbch\af31505\loch\f39 v2 }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Sensor or the software is defective) and to hold Microsoft and its affiliates harmless from such claims.
+\par }\pard \ltrpar\ql \li0\ri0\sb200\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 6. INDEMNIFICATION. }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 You agree to indemnify, \hich\af39\dbch\af31505\loch\f39 defend, and hold harmless Microsoft and its affiliates from any claims, including attorneys\hich\f39 \rquote \loch\f39
+ fees, related to the distribution or use of your Kinect }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid2042763 \hich\af39\dbch\af31505\loch\f39 v2}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+ Applications.
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 7. BACKUP COPY.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+ You may make one backup copy of the software. You may use it only to reins\hich\af39\dbch\af31505\loch\f39 tall the software.
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 8. DOCUMENTATION.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 Any person }{\rtlch\fcs1 \af39\afs20
+\ltrch\fcs0 \fs20\insrsid1909739 \hich\af39\dbch\af31505\loch\f39 who }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+has valid access to your computer or internal network may copy and use the documentation for your internal, reference purposes.
+\par }\pard \ltrpar\ql \fi-360\li360\ri0\sb200\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin360\itap0 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 9. SUPPORT SERVICES.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 \hich\f39 Because this software is \'93\loch\f39 \hich\f39 as is,\'94\loch\f39 we may not provide\hich\af39\dbch\af31505\loch\f39 support services for it.
+\par }\pard \ltrpar\ql \fi-360\li360\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin360\itap0 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 10.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\hich\af39\dbch\af31505\loch\f39 }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 EXPORT RESTRICTIONS.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+ The software is subject to United States export laws and regulations. You must comply with all domestic and international export laws and regulations that apply to the software. These laws include restrict\hich\af39\dbch\af31505\loch\f39
+ions on destinations, end users, and end use. For additional information, see }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\ul\cf2\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 www.microsoft.com/exporting}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 .}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\ul\insrsid8745415
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 11. ENTIRE AGREEMENT.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+ This agreement, and the terms for supplements, updates, Internet-based services and support services that you use, are the enti\hich\af39\dbch\af31505\loch\f39 re agreement for the software and support services.
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 12. APPLICABLE LAW.
+\par }\pard \ltrpar\ql \li360\ri0\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin360\itap0 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 a. United States.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+ If you acquired the software in the United States, Washington state law governs the interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws principles. The laws of the state where you live govern all other
+\hich\af39\dbch\af31505\loch\f39 c\hich\af39\dbch\af31505\loch\f39 laims, including claims under state consumer protection laws, unfair competition laws, and in tort.
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 b. Outside the United States.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+ If you acquired the software in any other country, the laws of that country apply.
+\par }\pard \ltrpar\ql \fi-360\li360\ri0\sb200\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin360\itap0 {\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 13. LEGAL EFFECT.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0
+\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 This agreement describes \hich\af39\dbch\af31505\loch\f39
+certain legal rights. You may have other rights under the laws of your state, province or country. This agreement does not change your rights under the laws of your state, province or country if the laws of your state, province or country do not permit it
+\hich\af39\dbch\af31505\loch\f39 \hich\af39\dbch\af31505\loch\f39 to do so.
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 \hich\f39 14. DISCLAIMER OF WARRANTY. The software is licensed \'93\loch\f39 \hich\f39 as-is.\'94\loch\f39
+ You bear all risk of using it. Microsoft gives no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws that this agreement cannot\hich\af39\dbch\af31505\loch\f39
+ change. To the extent permitted under your local laws, Microsoft excludes the implied warranties of merchantability, fitness for a particular purpose, and non-infringement.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 15. LIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES. You can recover from Micr\hich\af39\dbch\af31505\loch\f39
+osoft and its suppliers only direct damages up to U.S. $5.00. You cannot recover any other damages, including consequential, lost profits, special, indirect or incidental damages.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415
+\par }\pard \ltrpar\ql \li360\ri0\sa100\nowidctlpar\wrapdefault\faauto\rin0\lin360\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 This limitation applies to
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 a.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 anything related to the software, services, co
+\hich\af39\dbch\af31505\loch\f39 ntent (including code) on third party Internet sites, or third party programs; and
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39 b.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \fs20\insrsid8745415 \hich\af39\dbch\af31505\loch\f39
+ claims for breach of contract; breach of warranty, guarantee, or condition; strict liability, negligence, or other tort, to the extent permitted by applicable law.
+\par \hich\af39\dbch\af31505\loch\f39 It als\hich\af39\dbch\af31505\loch\f39
+o applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your state, province or country may not allow the exclusion or limitation of incidental, consequenti
+\hich\af39\dbch\af31505\loch\f39 a\hich\af39\dbch\af31505\loch\f39 l, or other damages.
+\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a
+9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad
+5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6
+b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0
+0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6
+a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f
+c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512
+0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462
+a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865
+6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b
+4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b
+4757e8d3f729e245eb2b260a0238fd010000ffff0300504b030414000600080000002100aa5225dfc60600008b1a0000160000007468656d652f7468656d652f
+7468656d65312e786d6cec595d8bdb46147d2ff43f08bd3bfe92fcb1c41b6cd9ceb6d94d42eca4e4716c8fadc98e344633de8d0981923c160aa569e943037deb
+43691b48a02fe9afd936a54d217fa17746b63c638fbb9b2585a5640d8b343af7ce997bafce1d4997afdc8fa87384134e58dc708b970aae83e3211b9178d2706f
+f7bbb99aeb7081e211a22cc60d778eb97b65f7c30f2ea31d11e2083b601ff31dd4704321a63bf93c1fc230e297d814c7706dcc920809384d26f951828ec16f44
+f3a542a1928f10895d274611b8bd311e932176fad2a5bbbb74dea1701a0b2e078634e949d7d8b050d8d1615122f89c0734718e106db830cf881df7f17de13a14
+7101171a6e41fdb9f9ddcb79b4b330a2628bad66d7557f0bbb85c1e8b0a4e64c26836c52cff3bd4a33f3af00546ce23ad54ea553c9fc29001a0e61a52917dda7
+dfaab7dafe02ab81d2438bef76b55d2e1a78cd7f798373d3973f03af40a97f6f03dfed06104503af4029dedfc07b5eb51478065e81527c65035f2d34db5ed5c0
+2b5048497cb8812ef89572b05c6d061933ba6785d77daf5b2d2d9caf50500d5975c929c62c16db6a2d42f758d2058004522448ec88f9148fd110aa3840940c12
+e2ec93490885374531e3305c2815ba8532fc973f4f1da988a01d8c346bc90b98f08d21c9c7e1c3844c45c3fd18bcba1ae4cdcb1fdfbc7cee9c3c7a71f2e89793
+c78f4f1efd9c3a32acf6503cd1ad5e7fffc5df4f3f75fe7afeddeb275fd9f15cc7fffed367bffdfaa51d082b5d85e0d5d7cffe78f1ecd5379ffff9c3130bbc99
+a0810eef930873e73a3e766eb10816a6426032c783e4ed2cfa2122ba45339e701423398bc57f478406fafa1c5164c1b5b019c13b09488c0d787576cf20dc0b93
+9920168fd7c2c8001e30465b2cb146e19a9c4b0b737f164fec9327331d770ba123dbdc018a8dfc766653d05662731984d8a07993a258a0098eb170e4357688b1
+6575770931e27a408609e36c2c9cbbc46921620d499f0c8c6a5a19ed9108f232b711847c1bb139b8e3b418b5adba8d8f4c24dc15885ac8f73135c27815cd048a
+6c2efb28a27ac0f791086d247bf364a8e33a5c40a6279832a733c29cdb6c6e24b05e2de9d7405eec693fa0f3c84426821cda7cee23c674649b1d06218aa6366c
+8fc4a18efd881f428922e7261336f80133ef10790e7940f1d674df21d848f7e96a701b9455a7b42a107965965872791533a37e7b733a4658490d08bfa1e71189
+4f15f73559f7ff5b5907217df5ed53cbaa2eaaa0371362bda3f6d6647c1b6e5dbc03968cc8c5d7ee369ac53731dc2e9b0decbd74bf976ef77f2fdddbeee7772f
+d82b8d06f9965bc574abae36eed1d67dfb9850da13738af7b9daba73e84ca32e0c4a3bf5cc8ab3e7b8690887f24e86090cdc2441cac64998f88488b017a229ec
+ef8bae7432e10bd713ee4c19876dbf1ab6fa96783a8b0ed8287d5c2d16e5a3692a1e1c89d578c1cfc6e15143a4e84a75f50896b9576c27ea51794940dabe0d09
+6d329344d942a2ba1c9441520fe610340b09b5b277c2a26e615193ee97a9da6001d4b2acc0d6c9810d57c3f53d30012378a242148f649ed2542fb3ab92f92e33
+bd2d984605c03e625901ab4cd725d7adcb93ab4b4bed0c99364868e566925091513d8c87688417d52947cf42e36d735d5fa5d4a02743a1e683d25ad1a8d6fe8d
+c579730d76ebda40635d2968ec1c37dc4ad9879219a269c31dc3633f1c4653a81d2eb7bc884ee0ddd95024e90d7f1e6599265cb4110fd3802bd149d520220227
+0e2551c395cbcfd24063a5218a5bb104827061c9d541562e1a3948ba99643c1ee3a1d0d3ae8dc848a7a7a0f0a95658af2af3f383a5259b41ba7be1e8d819d059
+720b4189f9d5a20ce0887078fb534ca33922f03a3313b255fdad35a685eceaef13550da5e3884e43b4e828ba98a77025e5191d7596c5403b5bac1902aa8564d1
+080713d960f5a01add34eb1a2987ad5df7742319394d34573dd35015d935ed2a66ccb06c036bb13c5f93d7582d430c9aa677f854bad725b7bed4bab57d42d625
+20e059fc2c5df70c0d41a3b69acca026196fcab0d4ecc5a8d93b960b3c85da599a84a6fa95a5dbb5b8653dc23a1d0c9eabf383dd7ad5c2d078b9af549156df3d
+f44f136c700fc4a30d2f81675470954af8f09020d810f5d49e24950db845ee8bc5ad0147ce2c210df741c16f7a41c90f72859adfc97965af90abf9cd72aee9fb
+e562c72f16daadd243682c228c8a7efacda50bafa2e87cf1e5458d6f7c7d89966fdb2e0d599467eaeb4a5e11575f5f8aa5ed5f5f1c02a2f3a052ead6cbf55625
+572f37bb39afddaae5ea41a5956b57826abbdb0efc5abdfbd0758e14d86b9603afd2a9e52ac520c8799582a45fabe7aa5ea9d4f4aacd5ac76b3e5c6c6360e5a9
+7c2c6201e155bc76ff010000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d652f7468656d652f5f72656c732f
+7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d363f2451eced0dae2c082e8761be
+9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e3198720e274a939cd08a54f980
+ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d9850528a2c6cce0239baa4c04ca5b
+babac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c0200001300000000000000000000000000000000005b436f6e74656e
+745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000000000000000300100005f72656c732f
+2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c00000000000000000000000000190200007468656d652f7468656d652f74
+68656d654d616e616765722e786d6c504b01022d0014000600080000002100aa5225dfc60600008b1a00001600000000000000000000000000d6020000746865
+6d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b0100002700000000000000000000000000d00900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000cb0a00000000}
+{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d
+617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169
+6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363
+656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e}
+{\*\latentstyles\lsdstimax371\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdlocked0 heading 1;\lsdqformat1 \lsdlocked0 heading 2;
+\lsdqformat1 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;
+\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;
+\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;
+\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;
+\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;
+\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;
+\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong;\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Table;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;
+\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Theme;\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;
+\lsdpriority62 \lsdlocked0 Light Grid;\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;
+\lsdpriority68 \lsdlocked0 Medium Grid 2;\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;
+\lsdpriority60 \lsdlocked0 Light Shading Accent 1;\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;
+\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;
+\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;
+\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;
+\lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;
+\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;
+\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;
+\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;
+\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;
+\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;
+\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;
+\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;
+\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5;
+\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5;
+\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5;
+\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6;
+\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6;
+\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6;
+\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis;
+\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography;
+\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4;
+\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4;
+\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1;
+\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1;
+\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2;
+\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2;
+\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3;
+\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4;
+\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4;
+\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5;
+\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5;
+\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6;
+\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6;
+\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark;
+\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1;
+\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1;
+\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2;
+\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3;
+\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3;
+\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4;
+\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4;
+\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5;
+\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5;
+\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6;
+\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;}}{\*\datastore 010500000200000018000000
+4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000
+d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000f062
+dc4024c2cf01feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file
diff --git a/3rdparty-notices/LIBUSB-COPYING.txt b/3rdparty-notices/LIBUSB-COPYING.txt
new file mode 100644
index 00000000..5ab7695a
--- /dev/null
+++ b/3rdparty-notices/LIBUSB-COPYING.txt
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/3rdparty-notices/ONNXRUNTIME.txt b/3rdparty-notices/ONNXRUNTIME.txt
new file mode 100644
index 00000000..7ebf5147
--- /dev/null
+++ b/3rdparty-notices/ONNXRUNTIME.txt
@@ -0,0 +1,24 @@
+The neuralnet tracker uses the ONNX library.
+See: <https://github.com/microsoft/onnxruntime>
+
+MIT License
+
+Copyright (c) Microsoft Corporation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. \ No newline at end of file
diff --git a/3rdparty-notices/ps3eyedriver/libusb0-AUTHORS.txt b/3rdparty-notices/ps3eyedriver/libusb0-AUTHORS.txt
new file mode 100644
index 00000000..3ad12f72
--- /dev/null
+++ b/3rdparty-notices/ps3eyedriver/libusb0-AUTHORS.txt
@@ -0,0 +1,20 @@
+This software is distributed under the following licenses:
+Driver: GNU General Public License (GPL)
+Library: GNU Lesser General Public (LGPL)
+Test Files: GNU Lesser General Public (LGPL)
+Filter Installer: GNU Lesser General Public (LGPL)
+
+Library, Test Programs:
+
+Stephan Meyer, <ste_meyer@web.de>
+Johannes Erdfelt, <johannes@erdfelt.com>
+Thomas Sailer, <sailer@ife.ee.ethz.ch>
+
+Drivers, Installer:
+
+Stephan Meyer, <ste_meyer@web.de>
+Travis Robinson, <libusbdotnet@gmail.com>
+
+Testing, Technical support:
+
+Xiaofan Chen, <xiaofanc@gmail.com>
diff --git a/3rdparty-notices/ps3eyedriver/libusbK-AUTHORS.txt b/3rdparty-notices/ps3eyedriver/libusbK-AUTHORS.txt
new file mode 100644
index 00000000..99f27a45
--- /dev/null
+++ b/3rdparty-notices/ps3eyedriver/libusbK-AUTHORS.txt
@@ -0,0 +1,40 @@
+CONTRIBUTORS
+-----------------------------------------------------------------------
+Development : Travis Lee Robinson (libusbdotnet@gmail.com)
+Testing : Xiaofan Chen (xiaofanc@gmail.com)
+
+LICENSE
+-----------------------------------------------------------------------
+Copyright (c) 2011-2012 Travis Lee Robinson. All rights reserved.
+
+APPLICABLE FOR ALL LIBUSBK BINARIES AND SOURCE CODE UNLESS OTHERWISE
+SPECIFIED. PLEASE SEE INDIVIDUAL COMPONENTS LICENSING TERMS FOR DETAILS.
+
+NOTE: Portions of dpscat use source code from libwdi which is licensed
+for LGPL use only. (See dpscat.c)
+
+NOTE: libusbK-inf-wizard.exe is linked to libwdi which is licensed for
+LGPL use only.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Travis Lee Robinson nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TRAVIS ROBINSON BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/3rdparty-notices/ps3eyedriver/libusbK-LICENSE-bsd.txt b/3rdparty-notices/ps3eyedriver/libusbK-LICENSE-bsd.txt
new file mode 100644
index 00000000..b80c667f
--- /dev/null
+++ b/3rdparty-notices/ps3eyedriver/libusbK-LICENSE-bsd.txt
@@ -0,0 +1,33 @@
+Copyright (c) 2011-2012 Travis Lee Robinson. All rights reserved.
+
+APPLICABLE FOR ALL LIBUSBK BINARIES AND SOURCE CODE UNLESS OTHERWISE
+SPECIFIED. PLEASE SEE INDIVIDUAL COMPONENTS LICENSING TERMS FOR DETAILS.
+
+NOTE: Portions of dpscat use source code from libwdi which is licensed
+for LGPL use only. (See dpscat.c)
+
+NOTE: libusbK-inf-wizard.exe is linked to libwdi which is licensed for
+LGPL use only.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Travis Lee Robinson nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TRAVIS ROBINSON BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/3rdparty-notices/ps3eyedriver/libusbK-LICENSE-libusb.txt b/3rdparty-notices/ps3eyedriver/libusbK-LICENSE-libusb.txt
new file mode 100644
index 00000000..e64f9687
--- /dev/null
+++ b/3rdparty-notices/ps3eyedriver/libusbK-LICENSE-libusb.txt
@@ -0,0 +1,22 @@
+/*
+ * Public libusb header file
+ * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com>
+ * Copyright © 2007-2008 Daniel Drake <dsd@gentoo.org>
+ * Copyright © 2012 Pete Batard <pete@akeo.ie>
+ * Copyright © 2012-2018 Nathan Hjelm <hjelmn@cs.unm.edu>
+ * For more information, please visit: http://libusb.info
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
diff --git a/3rdparty-notices/ps3eyedriver/ps3eyedriver-LICENSE.txt b/3rdparty-notices/ps3eyedriver/ps3eyedriver-LICENSE.txt
new file mode 100644
index 00000000..8e9ab2a0
--- /dev/null
+++ b/3rdparty-notices/ps3eyedriver/ps3eyedriver-LICENSE.txt
@@ -0,0 +1,45 @@
+License information for PS3EYEDriver
+------------------------------------
+
+The license of the PS3EYEDriver is MIT (for newly-written code) and GPLv2 for
+all code derived from the Linux Kernel Driver (ov534) sources.
+
+In https://github.com/inspirit/PS3EYEDriver/pull/3, Eugene Zatepyakin writes:
+
+ "all of my code is MIT licensed and to tell the truth i didnt check Linux
+ p3eye version license. as far as i know it was contributed to Linux by some
+ devs who decided to do it on their own..."
+
+The code is based on the Linux driver for the PSEye, which can be found here:
+
+http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/drivers/media/usb/gspca/ov534.c
+
+/*
+ * ov534-ov7xxx gspca driver
+ *
+ * Copyright (C) 2008 Antonio Ospite <ospite@studenti.unina.it>
+ * Copyright (C) 2008 Jim Paris <jim@jtan.com>
+ * Copyright (C) 2009 Jean-Francois Moine http://moinejf.free.fr
+ *
+ * Based on a prototype written by Mark Ferrell <majortrips@gmail.com>
+ * USB protocol reverse engineered by Jim Paris <jim@jtan.com>
+ * https://jim.sh/svn/jim/devl/playstation/ps3/eye/test/
+ *
+ * PS3 Eye camera enhanced by Richard Kaswy http://kaswy.free.fr
+ * PS3 Eye camera - brightness, contrast, awb, agc, aec controls
+ * added by Max Thrun <bear24rw@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
diff --git a/3rdparty-notices/ps3eyedriver/ps3eyedriver-opentrack-LICENSE.txt b/3rdparty-notices/ps3eyedriver/ps3eyedriver-opentrack-LICENSE.txt
new file mode 100644
index 00000000..65aa9106
--- /dev/null
+++ b/3rdparty-notices/ps3eyedriver/ps3eyedriver-opentrack-LICENSE.txt
@@ -0,0 +1,17 @@
+Licensing for PS3EYEDriver modifications:
+
+---
+
+Copyright (c) 2019, Stanislaw Halik <sthalik@misaki.pl>
+
+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.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/AUTHORS.md b/AUTHORS.md
index c6ab97d3..8016388a 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -8,5 +8,6 @@ chronological order:
- Michael Welter <<mw.pub@welter-4d.de>>
- Attila Csipa <<git@csipa.net>>
- Wei Shuai <<cpuwolf@gmail.com>>
+- Stéphane Lenclud <<github@lenclud.com>>
See OPENTRACK-LICENSING.txt for licensing information.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7859958a..c28ead74 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -21,24 +21,53 @@
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
-set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
-include(opentrack-check-build-directory)
-include(opentrack-mrproper)
+set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
-project(opentrack)
-cmake_minimum_required(VERSION 2.8.11)
+set(CMAKE_C_LINKER_PREFERENCE_PROPAGATES OFF)
+set(CMAKE_CXX_LINKER_PREFERENCE_PROPAGATES ON)
+
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/")
include(opentrack-policy NO_POLICY_SCOPE)
+
+cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
+project(opentrack)
+
+# must be prior to CMakeDetermineCXXCompiler due to rpath
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT OR NOT CMAKE_INSTALL_PREFIX)
+ set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "" FORCE)
+endif()
+
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "RELEASE" CACHE STRING "" FORCE)
+endif()
+
+if(POLICY CMP0083)
+ cmake_policy(SET CMP0083 NEW)
+ include(CheckPIESupported)
+ check_pie_supported()
+endif()
+
+include(CMakeDetermineCCompiler)
+include(CMakeDetermineCXXCompiler)
+
+if(CMAKE_COMPILER_IS_GNUCXX AND NOT __otr_compile_flags_set)
+ set(__otr_compile_flags_set TRUE CACHE INTERNAL "" FORCE)
+ foreach(i C CXX)
+ set(CMAKE_${i}_FLAGS_RELEASE "-O3 -march=native" CACHE STRING "" FORCE)
+ set(CMAKE_${i}_FLAGS "-ggdb -Wall -Wextra -Wpedantic" CACHE STRING "" FORCE)
+ endforeach()
+endif()
+
+include(CMakeParseArguments)
+
include(opentrack-load-user-settings)
+include(opentrack-mrproper)
set_property(GLOBAL PROPERTY opentrack-all-modules "")
set_property(GLOBAL PROPERTY opentrack-all-source-dirs "")
-set(opentrack_all-translations "" CACHE STRING "Leave empty for default")
-if(".${opentrack_all-translations}" STREQUAL ".")
- set(opentrack_all-translations "nl_NL;ru_RU;stub;zh_CN")
-endif()
+set(opentrack_all-translations "de_DE;nl_NL;ru_RU;stub;zh_CN")
-include(opentrack-word-size)
include(opentrack-hier)
include(opentrack-platform)
include(opentrack-i18n)
@@ -46,29 +75,61 @@ include(opentrack-boilerplate)
include(opentrack-qt)
include(opentrack-version)
-include(opentrack-install)
include(opentrack-variant)
if(WIN32)
enable_language(RC)
endif()
-add_custom_target(mrproper COMMAND
- "${CMAKE_COMMAND}" -P
- "${CMAKE_SOURCE_DIR}/cmake/opentrack-clean-build-directory.cmake"
+add_custom_target(mrproper
+ COMMAND "${CMAKE_COMMAND}" -P
+ "${CMAKE_SOURCE_DIR}/cmake/mrproper.cmake"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
function(otr_add_subdirs)
- otr_dist_select_variant()
+ otr_init_variant()
get_property(_globs GLOBAL PROPERTY opentrack-subprojects)
otr_add_target_dirs(_globbed ${_globs})
foreach(k ${_globbed})
get_filename_component(k "${k}" DIRECTORY)
- add_subdirectory("${k}")
+ # we want to compile macosx last so we can run proper install scripts
+ if (k MATCHES "macosx$")
+ set(o ${k})
+ else()
+ add_subdirectory("${k}")
+ endif()
endforeach()
+ # Add macosx last
+ add_subdirectory("${o}")
endfunction()
otr_add_subdirs()
otr_merge_translations()
-#install_sources()
+
+include(opentrack-install)
+
+message("Install directory:")
+message(" ${CMAKE_INSTALL_PREFIX}")
+string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE)
+message("Compile flags:")
+#foreach(j C CXX)
+foreach(j CXX)
+ foreach(i "" "_${CMAKE_BUILD_TYPE}")
+ message(" ${j}${i}: ${CMAKE_${j}_FLAGS${i}}")
+ endforeach()
+endforeach()
+
+message("Link flags:")
+foreach(j "" "_${CMAKE_BUILD_TYPE}")
+ #foreach(i EXE SHARED)
+ foreach(i SHARED)
+ message(" LINK_${i}${j}: ${CMAKE_${i}_LINKER_FLAGS${j}}")
+ endforeach()
+endforeach()
+
+message("Static archive flags:")
+foreach(k "" "_${CMAKE_BUILD_TYPE}")
+ message(" STATIC${k}: ${CMAKE_STATIC_LINKER_FLAGS${k}}")
+endforeach()
+message("--")
diff --git a/OPENTRACK-LICENSING.txt b/OPENTRACK-LICENSING.txt
index 2402beae..6155de68 100644
--- a/OPENTRACK-LICENSING.txt
+++ b/OPENTRACK-LICENSING.txt
@@ -81,6 +81,22 @@ 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.
+# Kinect Face Tracker & Easy Tracker
+
+Copyright (c) 2018-2019 Stéphane Lenclud
+
+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.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
# EOF
# vim: noai:ts=4:sw=4:tw=79
diff --git a/README.md b/README.md
index 4a6feab8..f04200b4 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,10 @@
+[<img src="https://github.com/opentrack/opentrack/actions/workflows/cmake.yml/badge.svg">](https://github.com/opentrack/opentrack/actions/workflows/cmake.yml)
+
## Intro
-opentrack project home at <<http://github.com/opentrack/opentrack>>.
+opentrack is a program for tracking user's head rotation and transmitting it to flight simulation software and military-themed video games. Project home is located at <<https://github.com/opentrack/opentrack>>.
+
+Looking for **railway planning software**? <<https://opentrack.ch>> had the name `opentrack` first. Apologies for the long-standing naming conflict.
For the latest **downloads** visit <<https://github.com/opentrack/opentrack/releases>> Download an `.exe` installer or a `.7z` archive. Currently installers and portable versions for Windows are available for each release. It supports [USB stick truly "portable" installations](https://github.com/opentrack/opentrack/wiki/portable-mode-for-USB-sticks)
@@ -8,10 +12,6 @@ Please first refer to <<https://github.com/opentrack/opentrack/wiki>>
for [new user guide](https://github.com/opentrack/opentrack/wiki/Quick-Start-Guide-(WIP)), [frequent answers](https://github.com/opentrack/opentrack/wiki/common-issues), specific tracker/filter
documentation. See also the [gameplay video](https://www.youtube.com/watch?v=XI73ul_FnBI) with opentrack set up.
-## Looking for railway planning software?
-
-**Railway planning software** <<http://opentrack.ch>> had the name `opentrack` first. Apologies for the long-standing naming conflict.
-
## Usage
`opentrack` is an application dedicated to tracking user's head
@@ -24,20 +24,22 @@ Don't be afraid to submit an **issue/feature request** if you have any problems!
## Tracking input
-- PointTracker by Patrick Ruoff, freetrack-like light sources
-- Oculus Rift DK1, DK2, CV, and legacy/knockoff versions (Windows only)
-- Paper [marker support](https://github.com/opentrack/opentrack/wiki/Aruco-tracker)
- via the ArUco library <<https://github.com/opentrack/aruco>>
+- PointTracker by Patrick Ruoff, FreeTrack-like light points
+- Oculus Rift (Windows only)
+- Paper [marker](https://github.com/opentrack/opentrack/wiki/Aruco-tracker) via the Aruco<sup>[[1](https://github.com/opentrack/aruco)]</sup> library
- Razer Hydra
- Relaying via UDP from a different computer
-- Relaying UDP via FreePIE-specific Android app
+- Relaying UDP via the FreePIE<sup>[[1](https://andersmalmgren.github.io/FreePIE/)]</sup> Android [apps](https://github.com/opentrack/opentrack/tree/master/contrib/freepie-udp)
- Joystick analog axes (Windows)
- Windows Phone [tracker](https://github.com/ZanderAdam/OpenTrack.WindowsPhone/wiki) over opentrack UDP protocol
-- Arduino with custom firmware
-- Intel RealSense 3D cameras (Windows)
+- Arduino with custom Hatire firmware
+- Intel RealSense 3D camera (Windows)
- BBC micro:bit, LEGO, sensortag support via Smalltalk<sup>[(1)](https://en.wikipedia.org/wiki/Smalltalk)[(2)](https://en.wikipedia.org/wiki/Alan_Kay)</sup>
- [S2Bot](http://www.picaxe.com/Teaching/Other-Software/Scratch-Helper-Apps/)
+ [S2Bot](https://www.picaxe.com/Teaching/Other-Software/Scratch-Helper-Apps/)
- Wiimote (Windows)
+- NeuralNet face tracker
+- Eyeware Beam<sup>[[1](https://beam.eyeware.tech/)]</sup>
+- Tobii eye tracker
## Output protocols
@@ -46,7 +48,7 @@ Don't be afraid to submit an **issue/feature request** if you have any problems!
- Relaying UDP to another computer
- Virtual joystick output (Windows, Linux, OSX)
- Wine freetrack glue protocol (Linux, OSX)
-- X-Plane plugin (Linux)
+- X-Plane plugin (Linux; uses the Wine output option)
- Tablet-like mouse output (Windows)
- FlightGear
- FSUIPC for Microsoft Flight Simulator 2002/2004 (Windows)
@@ -55,7 +57,7 @@ Don't be afraid to submit an **issue/feature request** if you have any problems!
## Credits, in chronological order
- Stanisław Halik (maintainer)
-- Wim Vriend -- author of [FaceTrackNoIR](http://facetracknoir.sourceforge.net/) that served as the initial codebase for `opentrack`. While the code was almost entirely rewritten, we still hold on to many of `FaceTrackNoIR`'s ideas.
+- Wim Vriend -- author of [FaceTrackNoIR](https://facetracknoir.sourceforge.net/) that served as the initial codebase for `opentrack`. While the code was almost entirely rewritten, we still hold on to many of `FaceTrackNoIR`'s ideas.
- Chris Thompson (aka mm0zct, Rift and Razer Hydra author and maintainer)
- Patrick Ruoff (PT tracker author)
- Xavier Hallade (Intel RealSense tracker author and maintainer)
@@ -65,15 +67,20 @@ Don't be afraid to submit an **issue/feature request** if you have any problems!
- Attila Csipa (Micro:Bit author)
- Eike "e4z9" (OSX joystick output driver)
- Wei Shuai (Wiimote tracker)
+- Stéphane Lenclud (Kinect Face Tracker, Easy Tracker)
+- GO63-samara (Hamilton Filter, Pose-widget improvement)
+- Davide Mameli (Eyeware Beam tracker)
+- Khoa Nguyen (Tobii eye tracker)
## Thanks
-- uglyDwarf (high CON)
+- uglyDwarf (of [linuxtrack](https://github.com/uglyDwarf/linuxtrack/))
- Andrzej Czarnowski (FreePIE tracker and
[Google Cardboard](https://github.com/opentrack/opentrack/wiki/VR-HMD-goggles-setup-----google-cardboard,-colorcross,-opendive)
assistance, testing)
- Wim Vriend (original codebase author and maintainer)
- Ryan Spicer (OSX tester, contributor)
+- Ries van Twisk (OSX tester, OSX Build Fixes, contributor)
- Donovan Baarda (filtering/control theory expert)
- Mathijs Groothuis (@MathijsG, dozens of bugs and other issues reported; NL translation)
- The Russian community from the [IL-2 Sturmovik forums](https://forum.il2sturmovik.ru/) (reporting bugs, requesting important features)
@@ -81,9 +88,9 @@ Don't be afraid to submit an **issue/feature request** if you have any problems!
## Contributing
-Code, translations,
+See guides for writing new modules\[[1](https://github.com/opentrack/opentrack/blob/master/api/plugin-api.hpp)\]\[[2](https://github.com/opentrack/opentrack/blob/master/tracker-test/test.h)\], and for [working with core code](https://github.com/opentrack/opentrack/wiki/Hacking-opentrack).
-Please see [basic rules for contributing](https://github.com/opentrack/opentrack/blob/unstable/CONTRIBUTING.md). There's also a guide for [working with core code](https://github.com/opentrack/opentrack/wiki/Hacking-opentrack). For writing input and output modules you don't need this guide except for
+To edit the wiki, send pull requests to the [opentrack/wiki](https://github.com/opentrack/wiki) repository. The [user-facing wiki](https://github.com/opentrack/opentrack/wiki) will automatically update itself once the commit is merged.
## License and warranty
diff --git a/api/CMakeLists.txt b/api/CMakeLists.txt
index 0a040a55..1454b89e 100644
--- a/api/CMakeLists.txt
+++ b/api/CMakeLists.txt
@@ -1,3 +1,2 @@
otr_module(api NO-COMPAT BIN)
-target_link_libraries(opentrack-api opentrack-options opentrack-compat)
diff --git a/api/lang/de_DE.ts b/api/lang/de_DE.ts
new file mode 100644
index 00000000..688fe72b
--- /dev/null
+++ b/api/lang/de_DE.ts
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>module_status_mixin</name>
+ <message>
+ <source>Unknown error</source>
+ <translation>Unbekannter Fehler</translation>
+ </message>
+</context>
+</TS>
diff --git a/api/lang/nl_NL.ts b/api/lang/nl_NL.ts
index 9e739505..91ba449c 100644
--- a/api/lang/nl_NL.ts
+++ b/api/lang/nl_NL.ts
@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="nl_NL">
+<context>
+ <name>module_status_mixin</name>
+ <message>
+ <source>Unknown error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/api/lang/ru_RU.ts b/api/lang/ru_RU.ts
index f62cf2e1..efaaaa47 100644
--- a/api/lang/ru_RU.ts
+++ b/api/lang/ru_RU.ts
@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
+<context>
+ <name>module_status_mixin</name>
+ <message>
+ <source>Unknown error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/api/lang/stub.ts b/api/lang/stub.ts
index 6401616d..d29cce1b 100644
--- a/api/lang/stub.ts
+++ b/api/lang/stub.ts
@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
+<context>
+ <name>module_status_mixin</name>
+ <message>
+ <source>Unknown error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/api/lang/zh_CN.ts b/api/lang/zh_CN.ts
index 6401616d..26bf67a5 100644
--- a/api/lang/zh_CN.ts
+++ b/api/lang/zh_CN.ts
@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>module_status_mixin</name>
+ <message>
+ <source>Unknown error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/api/plugin-api.cpp b/api/plugin-api.cpp
index 6c9a21bc..4d8d90e9 100644
--- a/api/plugin-api.cpp
+++ b/api/plugin-api.cpp
@@ -1,73 +1,77 @@
#include "plugin-api.hpp"
-#include "compat/macros.hpp"
+#include <QCloseEvent>
+#include <QDebug>
-using namespace plugin_api::detail;
+namespace plugin_api::detail {
-// these exist so that vtable is emitted in a single compilation unit, not all of them.
-
-Metadata::~Metadata() {}
-IFilter::~IFilter() {}
-IProtocol::~IProtocol() {}
-ITracker::~ITracker() {}
-IExtension::~IExtension() {}
-
-void ITrackerDialog::register_tracker(ITracker*) {}
-void ITrackerDialog::unregister_tracker() {}
-
-BaseDialog::BaseDialog() {}
-
-void BaseDialog::closeEvent(QCloseEvent*)
+BaseDialog::BaseDialog() = default;
+void BaseDialog::closeEvent(QCloseEvent* e)
{
if (isVisible())
- {
- hide();
emit closing();
- }
-}
-
-bool ITracker::center() { return false; }
-
-module_status ITracker::status_ok()
-{
- return module_status();
-}
-
-module_status ITracker::error(const QString& error)
-{
- return module_status(error);
+ e->accept();
}
-Metadata::Metadata() {}
-IFilter::IFilter() {}
-IFilterDialog::IFilterDialog() {}
-IProtocol::IProtocol() {}
-IProtocolDialog::IProtocolDialog() {}
-ITracker::ITracker() {}
-ITrackerDialog::ITrackerDialog() {}
-
void BaseDialog::done(int)
{
if (isVisible())
- {
- hide();
close();
- }
}
-IExtensionDialog::~IExtensionDialog()
-{
-}
+bool BaseDialog::embeddable() noexcept { return false; }
+void BaseDialog::save() {}
+void BaseDialog::reload() {}
+void BaseDialog::set_buttons_visible(bool) {}
+
+} // ns plugin_api::detail
+
+// these exist so that vtable is emitted in a single compilation unit, not all of them.
+
+Metadata_::Metadata_() = default;
+Metadata_::~Metadata_() = default;
+Metadata::Metadata() = default;
+Metadata::~Metadata() = default;
+
+IFilter::IFilter() = default;
+IFilter::~IFilter() = default;
+IFilterDialog::IFilterDialog() = default;
+IFilterDialog::~IFilterDialog() = default;
+void IFilterDialog::register_filter(IFilter*) {}
+void IFilterDialog::unregister_filter() {}
+IProtocol::IProtocol() = default;
+IProtocol::~IProtocol() = default;
+IProtocolDialog::IProtocolDialog() = default;
+IProtocolDialog::~IProtocolDialog() = default;
+void IProtocolDialog::register_protocol(IProtocol*){}
+void IProtocolDialog::unregister_protocol() {}
+ITracker::ITracker() = default;
+ITracker::~ITracker() = default;
+bool ITracker::center() { return false; }
+ITrackerDialog::ITrackerDialog() = default;
+ITrackerDialog::~ITrackerDialog() = default;
+void ITrackerDialog::register_tracker(ITracker*) {}
+void ITrackerDialog::unregister_tracker() {}
bool module_status::is_ok() const
{
return error.isNull();
}
-
+module_status_mixin::~module_status_mixin() = default;
module_status::module_status(const QString& error) : error(error) {}
-
-module_status module_status_mixin::status_ok() { return module_status(); }
+module_status::module_status() = default;
+module_status module_status_mixin::status_ok() { return {}; }
module_status module_status_mixin::error(const QString& error)
{
- return module_status(error.isEmpty() ? _("Unknown error") : error);
+ return module_status(error.isEmpty() ? tr("Unknown error") : error);
+}
+
+module_status ITracker::status_ok()
+{
+ return module_status();
+}
+
+module_status ITracker::error(const QString& error)
+{
+ return module_status_mixin::error(error);
}
diff --git a/api/plugin-api.hpp b/api/plugin-api.hpp
index 4a797f73..2d77bdf4 100644
--- a/api/plugin-api.hpp
+++ b/api/plugin-api.hpp
@@ -14,20 +14,26 @@
#include <QIcon>
#include <QWidget>
#include <QDialog>
+#include <QCoreApplication>
-#include "compat/simple-mat.hpp"
+#include "../compat/simple-mat.hpp"
+#include "../compat/tr.hpp"
#include "export.hpp"
using Pose = Mat<double, 6, 1>;
-enum Axis {
- TX, TY, TZ, Yaw, Pitch, Roll,
-
+enum Axis : int
+{
NonAxis = -1,
+ TX = 0, TY = 1, TZ = 2,
+
+ Yaw = 3, Pitch = 4, Roll = 5,
+ Axis_MIN = TX, Axis_MAX = 5,
+
+ Axis_COUNT = 6,
};
-namespace plugin_api {
-namespace detail {
+namespace plugin_api::detail {
class OTR_API_EXPORT BaseDialog : public QDialog
{
@@ -36,50 +42,62 @@ protected:
BaseDialog();
public:
void closeEvent(QCloseEvent *) override;
+ virtual bool embeddable() noexcept;
+ virtual void set_buttons_visible(bool x); // XXX TODO remove it once all modules are converted
+ virtual void save(); // XXX HACK should be pure virtual
+ virtual void reload(); // XXX HACK should be pure virtual -sh 20211214
signals:
void closing();
private slots:
void done(int) override;
};
-} // ns
-} // ns
+} // ns plugin_api::detail
#define OTR_PLUGIN_EXPORT OTR_GENERIC_EXPORT
#define OPENTRACK_DECLARE_PLUGIN_INTERNAL(ctor_class, ctor_ret_class, metadata_class, dialog_class, dialog_ret_class) \
- extern "C" OTR_PLUGIN_EXPORT ctor_ret_class* GetConstructor(); \
- extern "C" OTR_PLUGIN_EXPORT Metadata* GetMetadata(); \
- extern "C" OTR_PLUGIN_EXPORT dialog_ret_class* GetDialog(); \
- \
- extern "C" OTR_PLUGIN_EXPORT ctor_ret_class* GetConstructor() \
- { \
- return new ctor_class; \
- } \
- extern "C" OTR_PLUGIN_EXPORT Metadata* GetMetadata() \
- { \
- return new metadata_class; \
- } \
- extern "C" OTR_PLUGIN_EXPORT dialog_ret_class* GetDialog() \
- { \
- return new dialog_class; \
+ extern "C" \
+ { \
+ OTR_PLUGIN_EXPORT ctor_ret_class* GetConstructor(void); \
+ ctor_ret_class* GetConstructor(void) \
+ { \
+ return new ctor_class; \
+ } \
+ OTR_PLUGIN_EXPORT Metadata_* GetMetadata(void); \
+ Metadata_* GetMetadata(void) \
+ { \
+ return new metadata_class; \
+ } \
+ OTR_PLUGIN_EXPORT dialog_ret_class* GetDialog(void); \
+ dialog_ret_class* GetDialog(void) \
+ { \
+ return new dialog_class; \
+ } \
}
// implement this in all plugins
// also you must link against "opentrack-api" in CMakeLists.txt to avoid vtable link errors
-struct OTR_API_EXPORT Metadata
+class OTR_API_EXPORT Metadata_
{
- Metadata(const Metadata&) = delete;
- Metadata(Metadata&&) = delete;
- Metadata& operator=(const Metadata&) = delete;
- Metadata();
+public:
+ Metadata_();
// plugin name to be displayed in the interface
virtual QString name() = 0;
// plugin icon, you can return an empty QIcon()
virtual QIcon icon() = 0;
// optional destructor
- virtual ~Metadata();
+ virtual ~Metadata_();
+};
+
+class OTR_API_EXPORT Metadata : public TR, public Metadata_
+{
+ Q_OBJECT
+
+public:
+ Metadata();
+ ~Metadata() override;
};
struct OTR_API_EXPORT module_status final
@@ -87,7 +105,8 @@ struct OTR_API_EXPORT module_status final
QString error;
bool is_ok() const;
- module_status(const QString& error = QString());
+ module_status();
+ module_status(const QString& error);
};
/*
@@ -99,18 +118,20 @@ struct OTR_API_EXPORT module_status_mixin
static module_status error(const QString& error); // return error message on init failure
virtual module_status initialize() = 0; // where to return from
+ virtual ~module_status_mixin();
+
+ Q_DECLARE_TR_FUNCTIONS(module_status_mixin)
};
// implement this in filters
struct OTR_API_EXPORT IFilter : module_status_mixin
{
IFilter(const IFilter&) = delete;
- IFilter(IFilter&&) = delete;
IFilter& operator=(const IFilter&) = delete;
IFilter();
// optional destructor
- virtual ~IFilter();
+ ~IFilter() override;
// perform filtering step.
// you have to take care of dt on your own, try "opentrack-compat/timer.hpp"
virtual void filter(const double *input, double *output) = 0;
@@ -121,6 +142,7 @@ struct OTR_API_EXPORT IFilter : module_status_mixin
struct OTR_API_EXPORT IFilterDialog : public plugin_api::detail::BaseDialog
{
IFilterDialog();
+ ~IFilterDialog() override;
// optional destructor
//~IFilterDialog() override;
@@ -138,16 +160,14 @@ struct OTR_API_EXPORT IFilterDialog : public plugin_api::detail::BaseDialog
struct OTR_API_EXPORT IProtocol : module_status_mixin
{
IProtocol();
+ ~IProtocol() override;
IProtocol(const IProtocol&) = delete;
- IProtocol(IProtocol&&) = delete;
IProtocol& operator=(const IProtocol&) = delete;
- // optional destructor
- virtual ~IProtocol();
// called 250 times a second with XYZ yaw pitch roll pose
// try not to perform intense computation here. use a thread.
- virtual void pose(const double* headpose) = 0;
+ virtual void pose(const double* pose, const double* raw) = 0;
// return game name or placeholder text
virtual QString game_name() = 0;
};
@@ -162,6 +182,7 @@ struct OTR_API_EXPORT IProtocolDialog : public plugin_api::detail::BaseDialog
virtual void unregister_protocol() = 0;
IProtocolDialog();
+ ~IProtocolDialog() override;
};
// call once with your chosen class names in the plugin
@@ -187,7 +208,6 @@ struct OTR_API_EXPORT ITracker
static module_status error(const QString& error);
ITracker(const ITracker&) = delete;
- ITracker(ITracker&&) = delete;
ITracker& operator=(const ITracker&) = delete;
};
@@ -201,55 +221,10 @@ struct OTR_API_EXPORT ITrackerDialog : public plugin_api::detail::BaseDialog
virtual void unregister_tracker();
ITrackerDialog();
+ ~ITrackerDialog() override;
};
// call once with your chosen class names in the plugin
#define OPENTRACK_DECLARE_TRACKER(tracker_class, dialog_class, metadata_class) \
OPENTRACK_DECLARE_PLUGIN_INTERNAL(tracker_class, ITracker, metadata_class, dialog_class, ITrackerDialog)
-struct OTR_API_EXPORT IExtension : module_status_mixin
-{
- enum event_mask : unsigned
- {
- none = 0u,
- on_raw = 1 << 0,
- on_before_filter = 1 << 1,
- on_before_mapping = 1 << 2,
- on_finished = 1 << 3,
- };
-
- enum event_ordinal : unsigned
- {
- ev_raw = 0,
- ev_before_filter = 1,
- ev_before_mapping = 2,
- ev_finished = 3,
-
- event_count = 4,
- };
-
- IExtension() = default;
- virtual ~IExtension();
-
- virtual event_mask hook_types() = 0;
-
- virtual void process_raw(Pose&) {}
- virtual void process_before_filter(Pose&) {}
- virtual void process_before_mapping(Pose&) {}
- virtual void process_finished(Pose&) {}
-
- IExtension(const IExtension&) = delete;
- IExtension(IExtension&&) = delete;
- IExtension& operator=(const IExtension&) = delete;
-};
-
-struct OTR_API_EXPORT IExtensionDialog : public plugin_api::detail::BaseDialog
-{
- ~IExtensionDialog() override;
-
- virtual void register_extension(IExtension& ext) = 0;
- virtual void unregister_extension() = 0;
-};
-
-#define OPENTRACK_DECLARE_EXTENSION(ext_class, dialog_class, metadata_class) \
- OPENTRACK_DECLARE_PLUGIN_INTERNAL(ext_class, IExtension, metadata_class, dialog_class, IExtensionDialog)
diff --git a/api/plugin-support.hpp b/api/plugin-support.hpp
index 8fc01b98..4300da18 100644
--- a/api/plugin-support.hpp
+++ b/api/plugin-support.hpp
@@ -8,135 +8,124 @@
#pragma once
#include "plugin-api.hpp"
+#include "compat/library-path.hpp"
#include <memory>
#include <algorithm>
#include <cstring>
+#include <vector>
#include <QDebug>
#include <QString>
#include <QLibrary>
-#include <QList>
#include <QDir>
-#include <QList>
#include <QIcon>
-#if defined(__APPLE__)
-# define OPENTRACK_SOLIB_EXT "dylib"
-#elif defined(_WIN32)
-# define OPENTRACK_SOLIB_EXT "dll"
-#else
-# define OPENTRACK_SOLIB_EXT "so"
-#endif
+extern "C" {
+ using module_ctor_t = void* (*)(void);
+ using module_metadata_t = Metadata_* (*)(void);
+}
-#define OPENTRACK_SOLIB_PREFIX "lib"
+enum dylib_load_mode : unsigned
+{
+ dylib_load_norm = 0,
+ dylib_load_quiet = 1 << 0,
+ dylib_load_none = 1 << 1,
+};
-extern "C" typedef void* (*OPENTRACK_CTOR_FUNPTR)(void);
-extern "C" typedef Metadata* (*OPENTRACK_METADATA_FUNPTR)(void);
+enum class dylib_type : unsigned
+{
+ Filter = 0xdeadbabe,
+ Tracker = 0xcafebeef,
+ Protocol = 0xdeadf00d,
+ Video = 0xbadf00d,
+ Invalid = (unsigned)-1,
+};
struct dylib final
{
- enum Type : unsigned
- {
- Filter = 0xdeadbabeu,
- Tracker = 0xcafebeefu,
- Protocol = 0xdeadf00du,
- Extension = 0xdeadf001u,
- Invalid = 0xcafebabeu,
- };
-
- dylib(const QString& filename_, Type t) :
- type(Invalid),
+ dylib(const QString& filename_, dylib_type t, dylib_load_mode load_mode = dylib_load_norm) :
full_filename(filename_),
- module_name(trim_filename(filename_)),
- Dialog(nullptr),
- Constructor(nullptr),
- Meta(nullptr)
+ module_name(trim_filename(filename_))
{
// otherwise dlopen opens the calling executable
- if (filename_.size() == 0 || module_name.size() == 0)
+ if (filename_.isEmpty() || module_name.isEmpty())
return;
handle.setFileName(filename_);
- handle.setLoadHints(QLibrary::DeepBindHint | QLibrary::PreventUnloadHint);
-
- if (check(!handle.load()))
- return;
-
- if (check((Dialog = (OPENTRACK_CTOR_FUNPTR) handle.resolve("GetDialog"), !Dialog)))
- return;
+ handle.setLoadHints(QLibrary::DeepBindHint | QLibrary::ResolveAllSymbolsHint);
- if (check((Constructor = (OPENTRACK_CTOR_FUNPTR) handle.resolve("GetConstructor"), !Constructor)))
- return;
-
- if (check((Meta = (OPENTRACK_METADATA_FUNPTR) handle.resolve("GetMetadata"), !Meta)))
- return;
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wcomma"
+#endif
- auto m = std::unique_ptr<Metadata>(Meta());
+ if (!handle.load())
+ goto fail;
- icon = m->icon();
- name = m->name();
+ if (!(load_mode & dylib_load_none))
+ {
+ std::unique_ptr<Metadata_> m;
- type = t;
- }
- ~dylib()
- {
- // QLibrary refcounts the .dll's so don't forcefully unload
- }
+ if (Dialog = (module_ctor_t) handle.resolve("GetDialog"), !Dialog)
+ goto fail;
- static QList<std::shared_ptr<dylib>> enum_libraries(const QString& library_path)
- {
- QDir module_directory(library_path);
- QList<std::shared_ptr<dylib>> ret;
+ if (Constructor = (module_ctor_t) handle.resolve("GetConstructor"), !Constructor)
+ goto fail;
- using str = QLatin1String;
+ if (Meta = (module_metadata_t) handle.resolve("GetMetadata"), !Meta)
+ goto fail;
- static const struct filter_ {
- Type type;
- QLatin1String glob;
- } filters[] = {
- { Filter, str(OPENTRACK_SOLIB_PREFIX "opentrack-filter-*." OPENTRACK_SOLIB_EXT), },
- { Tracker, str(OPENTRACK_SOLIB_PREFIX "opentrack-tracker-*." OPENTRACK_SOLIB_EXT), },
- { Protocol, str(OPENTRACK_SOLIB_PREFIX "opentrack-proto-*." OPENTRACK_SOLIB_EXT), },
- { Extension, str(OPENTRACK_SOLIB_PREFIX "opentrack-ext-*." OPENTRACK_SOLIB_EXT), },
- };
+ m = std::unique_ptr<Metadata_>(Meta());
- for (const filter_& filter : filters)
- {
- for (const QString& filename : module_directory.entryList({ filter.glob }, QDir::Files, QDir::Name))
+ if (!m)
{
- std::shared_ptr<dylib> lib = std::make_shared<dylib>(QStringLiteral("%1/%2").arg(library_path).arg(filename), filter.type);
-
- if (lib->type == Invalid)
- continue;
-
- if (std::any_of(ret.cbegin(),
- ret.cend(),
- [&lib](const std::shared_ptr<dylib>& a) {
- return a->type == lib->type && a->name == lib->name;
- }))
+ if (!(load_mode & dylib_load_quiet))
{
- qDebug() << "duplicate lib" << filename << "ident" << lib->name;
- continue;
+ qDebug() << "library" << module_name << "failed: no metadata";
+ load_mode = dylib_load_quiet;
}
-
- ret.push_back(lib);
+ goto fail;
}
+
+ icon = m->icon();
+ name = m->name();
}
+ else
+ name = module_name;
- return ret;
+ type = t;
+
+ return;
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
+fail:
+ if (!(load_mode & dylib_load_quiet))
+ qDebug() << "library" << module_name << "failed:" << handle.errorString();
+
+ Constructor = nullptr;
+ Dialog = nullptr;
+ Meta = nullptr;
+
+ type = dylib_type::Invalid;
}
- Type type;
+ // QLibrary refcounts the .dll's so don't forcefully unload
+ ~dylib() = default;
+
+ dylib_type type = dylib_type::Invalid;
QString full_filename;
QString module_name;
QIcon icon;
QString name;
- OPENTRACK_CTOR_FUNPTR Dialog;
- OPENTRACK_CTOR_FUNPTR Constructor;
- OPENTRACK_METADATA_FUNPTR Meta;
+ module_ctor_t Dialog = nullptr;
+ module_ctor_t Constructor = nullptr;
+ module_metadata_t Meta = nullptr;
+
private:
QLibrary handle;
@@ -150,21 +139,21 @@ private:
{
in = in.mid(idx + 1);
- if (in.startsWith(OPENTRACK_SOLIB_PREFIX) &&
- in.endsWith("." OPENTRACK_SOLIB_EXT))
+ if (in.startsWith(OPENTRACK_LIBRARY_PREFIX) &&
+ in.endsWith("." OPENTRACK_LIBRARY_EXTENSION))
{
- constexpr unsigned pfx_len = sizeof(OPENTRACK_SOLIB_PREFIX) - 1;
- constexpr unsigned rst_len = sizeof("." OPENTRACK_SOLIB_EXT) - 1;
+ constexpr unsigned pfx_len = sizeof(OPENTRACK_LIBRARY_PREFIX) - 1;
+ constexpr unsigned rst_len = sizeof("." OPENTRACK_LIBRARY_EXTENSION) - 1;
in = in.mid(pfx_len);
in = in.left(in.size() - rst_len);
- static constexpr const char* const names[] =
+ const char* const names[] =
{
- "opentrack-tracker-",
- "opentrack-proto-",
- "opentrack-filter-",
- "opentrack-ext-",
+ OPENTRACK_LIBRARY_PREFIX "opentrack-tracker-",
+ OPENTRACK_LIBRARY_PREFIX "opentrack-proto-",
+ OPENTRACK_LIBRARY_PREFIX "opentrack-filter-",
+ OPENTRACK_LIBRARY_PREFIX "opentrack-video-",
};
for (auto name : names)
@@ -174,74 +163,104 @@ private:
}
}
}
- return QString();
- }
-
- bool check(bool fail)
- {
- if (fail)
- {
- qDebug() << "library" << module_name << "failed:" << handle.errorString();
-
- if (handle.isLoaded())
- (void) handle.unload();
-
- Constructor = nullptr;
- Dialog = nullptr;
- Meta = nullptr;
-
- type = Invalid;
- }
-
- return fail;
+ return {""};
}
};
struct Modules final
{
using dylib_ptr = std::shared_ptr<dylib>;
- using dylib_list = QList<dylib_ptr>;
-
- Modules(const QString& library_path) :
- module_list(dylib::enum_libraries(library_path)),
- filter_modules(filter(dylib::Filter)),
- tracker_modules(filter(dylib::Tracker)),
- protocol_modules(filter(dylib::Protocol)),
- extension_modules(filter(dylib::Extension))
+ using dylib_list = std::vector<dylib_ptr>;
+ using type = dylib_type;
+
+ Modules(const QString& library_path, dylib_load_mode load_mode = dylib_load_norm) :
+ module_list(enum_libraries(library_path, load_mode)),
+ filter_modules(filter(type::Filter)),
+ tracker_modules(filter(type::Tracker)),
+ protocol_modules(filter(type::Protocol)),
+ video_modules(filter(type::Video))
{}
dylib_list& filters() { return filter_modules; }
dylib_list& trackers() { return tracker_modules; }
dylib_list& protocols() { return protocol_modules; }
- dylib_list& extensions() { return extension_modules; }
+
private:
dylib_list module_list;
dylib_list filter_modules;
dylib_list tracker_modules;
dylib_list protocol_modules;
- dylib_list extension_modules;
+ dylib_list video_modules;
static dylib_list& sorted(dylib_list& xs)
{
- std::sort(xs.begin(), xs.end(), [&](const dylib_ptr& a, const dylib_ptr& b) { return a->name.toLower() < b->name.toLower(); });
+ std::sort(xs.begin(), xs.end(),
+ [&](const dylib_ptr& a, const dylib_ptr& b) {
+ return a->name.toLower() < b->name.toLower();
+ });
return xs;
}
- dylib_list filter(dylib::Type t)
+ dylib_list filter(dylib_type t)
{
- QList<std::shared_ptr<dylib>> ret;
- for (auto x : module_list)
+ dylib_list ret; ret.reserve(module_list.size());
+ for (const auto& x : module_list)
if (x->type == t)
ret.push_back(x);
return sorted(ret);
}
+
+ static dylib_list enum_libraries(const QString& library_path,
+ dylib_load_mode load_mode = dylib_load_norm)
+ {
+ QDir dir(library_path);
+ dylib_list ret;
+
+ const struct filter_ {
+ type t = type::Invalid;
+ QString glob;
+ dylib_load_mode load_mode = dylib_load_norm;
+ } filters[] = {
+ { type::Filter, OPENTRACK_LIBRARY_PREFIX "opentrack-filter-*." OPENTRACK_LIBRARY_EXTENSION, },
+ { type::Tracker, OPENTRACK_LIBRARY_PREFIX "opentrack-tracker-*." OPENTRACK_LIBRARY_EXTENSION, },
+ { type::Protocol, OPENTRACK_LIBRARY_PREFIX "opentrack-proto-*." OPENTRACK_LIBRARY_EXTENSION, },
+ { type::Video, OPENTRACK_LIBRARY_PREFIX "opentrack-video-*." OPENTRACK_LIBRARY_EXTENSION, dylib_load_none, },
+ };
+
+ for (const filter_& filter : filters)
+ {
+ for (const QString& filename : dir.entryList({ filter.glob }, QDir::Files, QDir::Name))
+ {
+ dylib_load_mode load_mode_{filter.load_mode | load_mode};
+ auto lib = std::make_shared<dylib>(QString("%1/%2").arg(library_path, filename), filter.t, load_mode_);
+
+ if (lib->type == type::Invalid)
+ continue;
+
+ if (std::any_of(ret.cbegin(),
+ ret.cend(),
+ [&lib](const std::shared_ptr<dylib>& a) {
+ return a->type == lib->type && a->name == lib->name;
+ }))
+ {
+ if (!(load_mode & dylib_load_quiet))
+ qDebug() << "duplicate lib" << filename << "ident" << lib->name;
+ continue;
+ }
+
+ ret.push_back(std::move(lib));
+ }
+ }
+
+ return ret;
+ }
};
template<typename t>
-static inline std::shared_ptr<t> make_dylib_instance(const std::shared_ptr<dylib>& lib)
+std::shared_ptr<t> make_dylib_instance(const std::shared_ptr<dylib>& lib)
{
- std::shared_ptr<t> ret;
if (lib != nullptr && lib->Constructor)
- ret = std::shared_ptr<t>(reinterpret_cast<t*>(reinterpret_cast<OPENTRACK_CTOR_FUNPTR>(lib->Constructor)()));
- return ret;
+ return std::shared_ptr<t>{(t*)lib->Constructor()};
+ else
+ return nullptr;
}
diff --git a/bin/NPClient.dll b/bin/NPClient.dll
index 912cd1a2..bf971c03 100644
--- a/bin/NPClient.dll
+++ b/bin/NPClient.dll
Binary files differ
diff --git a/bin/NPClient64.dll b/bin/NPClient64.dll
index e8e01cfc..fd3164e5 100644
--- a/bin/NPClient64.dll
+++ b/bin/NPClient64.dll
Binary files differ
diff --git a/bin/build-msvc.cmd b/bin/build-msvc.cmd
index 4adad019..4c3d90b4 100644
--- a/bin/build-msvc.cmd
+++ b/bin/build-msvc.cmd
@@ -1,26 +1,26 @@
-@setlocal ENABLEDELAYEDEXPANSION
-@echo off
-
-call msvc
-
-taskkill -f -im trackir.exe 2>%SystemDrive%\NUL
-
-call:check cmake .
-call:check ninja i18n
-call:check "d:/cygwin64/bin/dash.exe" -c "git checkout -f -- ../*/lang/ ../*/*/lang/"
-call:check ninja -j6 install
-call:check "d:/cygwin64/bin/dash.exe" -c "git checkout -f -- ../*/lang/ ../*/*/lang/"
-
-exit /b 0
-
-:check
-
-cmd /c "msvc d:/cygwin64/bin/nice.exe -n 20 -- %*"
-
-if %ERRORLEVEL% EQU 0 (
- GOTO:EOF
-)
-
-rem echo error %ERRORLEVEL%
-exit %ERRORLEVEL%
-
+@setlocal ENABLEDELAYEDEXPANSION
+@echo off
+
+call msvc
+
+taskkill -f -im trackir.exe 2>%SystemDrive%\NUL
+
+call:check cmake .
+call:check ninja i18n
+call:check "d:/cygwin64/bin/dash.exe" -c "git checkout -f -- ../*/lang/ ../*/*/lang/"
+call:check ninja -j6 install
+call:check "d:/cygwin64/bin/dash.exe" -c "git checkout -f -- ../*/lang/ ../*/*/lang/"
+
+exit /b 0
+
+:check
+
+cmd /c "msvc d:/cygwin64/bin/nice.exe -n 20 -- %*"
+
+if %ERRORLEVEL% EQU 0 (
+ GOTO:EOF
+)
+
+rem echo error %ERRORLEVEL%
+exit %ERRORLEVEL%
+
diff --git a/bin/cleye.config b/bin/cleye.config
index 55a478ab..bfb37b47 100644
--- a/bin/cleye.config
+++ b/bin/cleye.config
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<cleye>
- <item name="mode" value="advanced" />
+<?xml version="1.0" encoding="UTF-8"?>
+<cleye>
+ <item name="mode" value="advanced" />
</cleye> \ No newline at end of file
diff --git a/bin/freetrackclient64.dll b/bin/freetrackclient64.dll
new file mode 100644
index 00000000..e1074592
--- /dev/null
+++ b/bin/freetrackclient64.dll
Binary files differ
diff --git a/cmake/FindEigen3.cmake b/cmake/FindEigen3.cmake
deleted file mode 100644
index 2206ce43..00000000
--- a/cmake/FindEigen3.cmake
+++ /dev/null
@@ -1,84 +0,0 @@
-# - Try to find Eigen3 lib
-#
-# This module supports requiring a minimum version, e.g. you can do
-# find_package(Eigen3 3.1.2)
-# to require version 3.1.2 or newer of Eigen3.
-#
-# Once done this will define
-#
-# EIGEN3_FOUND - system has eigen lib with correct version
-# EIGEN3_INCLUDE_DIR - the eigen include directory
-# EIGEN3_VERSION - eigen version
-
-# Copyright (c) 2006, 2007 Montel Laurent, <montel@kde.org>
-# Copyright (c) 2008, 2009 Gael Guennebaud, <g.gael@free.fr>
-# Copyright (c) 2009 Benoit Jacob <jacob.benoit.1@gmail.com>
-# Redistribution and use is allowed according to the terms of the 2-clause BSD license.
-
-if(NOT Eigen3_FIND_VERSION)
- if(NOT Eigen3_FIND_VERSION_MAJOR)
- set(Eigen3_FIND_VERSION_MAJOR 2)
- endif(NOT Eigen3_FIND_VERSION_MAJOR)
- if(NOT Eigen3_FIND_VERSION_MINOR)
- set(Eigen3_FIND_VERSION_MINOR 91)
- endif(NOT Eigen3_FIND_VERSION_MINOR)
- if(NOT Eigen3_FIND_VERSION_PATCH)
- set(Eigen3_FIND_VERSION_PATCH 0)
- endif(NOT Eigen3_FIND_VERSION_PATCH)
-
- set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}")
-endif(NOT Eigen3_FIND_VERSION)
-
-macro(_eigen3_check_version)
- file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header)
-
- string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}")
- set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}")
- string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}")
- set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}")
- string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}")
- set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}")
-
- set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION})
- if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
- set(EIGEN3_VERSION_OK FALSE)
- else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
- set(EIGEN3_VERSION_OK TRUE)
- endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
-
- if(NOT EIGEN3_VERSION_OK)
-
- message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, "
- "but at least version ${Eigen3_FIND_VERSION} is required")
- endif(NOT EIGEN3_VERSION_OK)
-endmacro(_eigen3_check_version)
-
-if (EIGEN3_INCLUDE_DIR)
-
- # in cache already
- _eigen3_check_version()
- set(EIGEN3_FOUND ${EIGEN3_VERSION_OK})
-
-else (EIGEN3_INCLUDE_DIR)
-
- find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library
- PATHS
- ${CMAKE_INSTALL_PREFIX}/include
- ${KDE4_INCLUDE_DIR}
- PATH_SUFFIXES eigen3 eigen
- )
-
- if(EIGEN3_INCLUDE_DIR)
- _eigen3_check_version()
- endif(EIGEN3_INCLUDE_DIR)
-
- #include(FindPackageHandleStandardArgs)
- #find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK)
-
- mark_as_advanced(EIGEN3_INCLUDE_DIR)
-
-endif(EIGEN3_INCLUDE_DIR)
-
-if(EIGEN3_FOUND)
- add_definitions(-DEIGEN_MPL2_ONLY)
-endif()
diff --git a/cmake/FindONNXRuntime.cmake b/cmake/FindONNXRuntime.cmake
new file mode 100644
index 00000000..60ddfc2b
--- /dev/null
+++ b/cmake/FindONNXRuntime.cmake
@@ -0,0 +1,104 @@
+# FindONNXRuntime
+# ===============
+#
+# Find an ONNX Runtime installation.
+# ONNX Runtime is a cross-platform inference and training machine-learning
+# accelerator.
+#
+# Input variables
+# ---------------
+#
+# ONNXRuntime_ROOT Set root installation. This is an environment
+# variable
+# ONNXRuntime_DIR CMake variable to set the installation root.
+#
+# Output variable
+# ---------------
+#
+# ONNXRuntime_FOUND True if headers and requested libraries were found
+# ONNXRuntime_LIBRARIES Component libraries to be linked.
+# ONNXRuntime_INCLUDE_DIRS Include directories.
+
+set(ONNXRuntime_DIR CACHE PATH "Root directory of the ONNX Runtime installation")
+
+# This script is mostly inspired by the guide there:
+# https://gitlab.kitware.com/cmake/community/-/wikis/doc/tutorials/How-To-Find-Libraries
+# Finding of the DLLs was added so we can install them with the application.
+# We adhere to modern CMake standards and also define an import library.
+
+# Determine architecture string for subdir in nuget package
+# This is only relevant when we point `ONNXRuntime_DIR` to the
+# extracted nuget package content.
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ if (CMAKE_SIZEOF_VOID_P GREATER_EQUAL 8)
+ set(ONNXRuntime_Arch "linux-x64")
+ else()
+ message(FATAL_ERROR "32 Bit Linux builds are not supported")
+ endif()
+elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+ if (CMAKE_SIZEOF_VOID_P GREATER_EQUAL 8)
+ set(ONNXRuntime_Arch "win-x64")
+ else()
+ set(ONNXRuntime_Arch "win-x86")
+ endif()
+endif()
+
+
+find_library(ONNXRuntime_LIBRARY onnxruntime
+ HINTS ${ONNXRuntime_DIR}
+ PATH_SUFFIXES
+ # For a "normal" installation
+ "lib" "lib64" "bin"
+ # For the nuget package
+ "runtimes/${ONNXRuntime_Arch}/native"
+ )
+
+if(WIN32)
+ SET(CMAKE_FIND_LIBRARY_SUFFIXES ".dll" ".DLL")
+ find_library(ONNXRuntime_RUNTIME onnxruntime
+ HINTS ${ONNXRuntime_DIR}
+ PATH_SUFFIXES
+ # For a "normal" installation
+ "lib" "lib64" "bin"
+ # For the nuget package
+ "runtimes/${ONNXRuntime_Arch}/native"
+ )
+else()
+ SET(ONNXRuntime_RUNTIME ${ONNXRuntime_LIBRARY})
+endif()
+
+find_path(ONNXRuntime_INCLUDE_DIR onnxruntime_cxx_api.h
+ HINTS ${ONNXRuntime_DIR}
+ PATH_SUFFIXES
+ # For .nuget packages
+ "build/native/include"
+ # For when the directory structure of the onnx source repo is preserved
+ "include/onnxruntime/core/session"
+ "include/onnxruntime"
+ # For when we copy the files somewhere
+ "include"
+ )
+
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set ONNXRuntime_FOUND to TRUE
+# if all listed variables are TRUE
+find_package_handle_standard_args(ONNXRuntime DEFAULT_MSG
+ ONNXRuntime_LIBRARY ONNXRuntime_INCLUDE_DIR ONNXRuntime_RUNTIME)
+
+if(ONNXRuntime_FOUND)
+ if(NOT TARGET onnxruntime::onnxruntime)
+ # Following this quide:
+ # https://cmake.org/cmake/help/git-stage/guide/importing-exporting/index.html#importing-libraries
+ add_library(onnxruntime::onnxruntime SHARED IMPORTED)
+ set_target_properties(onnxruntime::onnxruntime PROPERTIES
+ IMPORTED_LOCATION "${ONNXRuntime_RUNTIME}"
+ INTERFACE_INCLUDE_DIRECTORIES "${ONNXRuntime_INCLUDE_DIR}"
+ IMPORTED_IMPLIB "${ONNXRuntime_LIBRARY}")
+ endif()
+endif()
+
+mark_as_advanced(ONNXRuntime_INCLUDE_DIR ONNXRuntime_LIBRARY ONNXRuntime_RUNTIME)
+
+set(ONNXRuntime_INCLUDE_DIRS ${ONNXRuntime_INCLUDE_DIR})
+set(ONNXRuntime_LIBRARIES ${ONNXRuntime_LIBRARY}) \ No newline at end of file
diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake
index e4ac79c0..0c016cc0 100644
--- a/cmake/GetGitRevisionDescription.cmake
+++ b/cmake/GetGitRevisionDescription.cmake
@@ -30,6 +30,8 @@
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
+include_guard(GLOBAL)
+
if(__get_git_revision_description)
return()
endif()
diff --git a/cmake/GetGitRevisionDescription.cmake.in b/cmake/GetGitRevisionDescription.cmake.in
index 888ce13a..032dda66 100644
--- a/cmake/GetGitRevisionDescription.cmake.in
+++ b/cmake/GetGitRevisionDescription.cmake.in
@@ -13,6 +13,8 @@
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
+include_guard(GLOBAL)
+
set(HEAD_HASH)
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
diff --git a/cmake/apple.cmake b/cmake/apple.cmake
index 627ef3c1..64e4d628 100644
--- a/cmake/apple.cmake
+++ b/cmake/apple.cmake
@@ -1,12 +1,19 @@
set(CMAKE_BUILD_TYPE_INIT RELEASE)
-set(CMAKE_CXX_FLAGS "-arch x86_64 -stdlib=libc++" CACHE STRING "" FORCE)
-set(CMAKE_C_FLAGS "-arch x86_64" CACHE STRING "" FORCE)
+if("$ENV{OTR_OSX_ARCH}" STREQUAL "arm64")
+ set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "" FORCE)
+ set(opentrack-intel FALSE)
+else()
+ set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE STRING "" FORCE)
+ set(opentrack-intel TRUE)
+endif()
-set(CMAKE_C_FLAGS_RELEASE "-ffast-math -O3 -flto -fvisibility=hidden -g" CACHE STRING "" FORCE)
+set(CMAKE_CXX_FLAGS "-stdlib=libc++" CACHE STRING "" FORCE)
+
+set(CMAKE_C_FLAGS_RELEASE "-ffast-math -O3 -flto -g" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS_RELEASE " ${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "" FORCE)
-set(cmake-link-common "-stdlib=libc++ -arch x86_64")
+set(cmake-link-common "-stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS "${cmake-link-common} -Wl,-stack_size,0x4000000" CACHE STRING "" FORCE)
set(CMAKE_SHARED_LINKER_FLAGS ${cmake-link-common} CACHE STRING "" FORCE)
set(CMAKE_MODULE_LINKER_FLAGS ${cmake-link-common} CACHE STRING "" FORCE)
diff --git a/cmake/linux.cmake b/cmake/linux.cmake
index 945f7e77..5513f511 100644
--- a/cmake/linux.cmake
+++ b/cmake/linux.cmake
@@ -1,13 +1,13 @@
set(CMAKE_BUILD_TYPE_INIT RELEASE)
-set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "" FORCE)
-set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "" FORCE)
+set(CMAKE_CXX_FLAGS "" CACHE STRING "" FORCE)
+set(CMAKE_C_FLAGS "" CACHE STRING "" FORCE)
set(CMAKE_AR "gcc-ar" CACHE STRING "" FORCE)
set(CMAKE_NM "gcc-nm" CACHE STRING "" FORCE)
set(CMAKE_RANLIB "gcc-ranlib" CACHE STRING "" FORCE)
-set(CMAKE_C_FLAGS_RELEASE "-ffast-math -O3 -flto -fuse-linker-plugin -fvisibility=hidden -fPIC" CACHE STRING "" FORCE)
+set(CMAKE_C_FLAGS_RELEASE "-ffast-math -O3 -flto -fuse-linker-plugin -fPIC" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS_RELEASE " ${CMAKE_C_FLAGS_RELEASE} " CACHE STRING "" FORCE)
set(cmake-link-common "")
diff --git a/cmake/mingw-w64.cmake b/cmake/mingw-w64.cmake
index d32f4a1c..e8c0f062 100644
--- a/cmake/mingw-w64.cmake
+++ b/cmake/mingw-w64.cmake
@@ -3,10 +3,21 @@
# mkdir build && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=$(pwd)/../cmake/mingw-w64.cmake
# -sh 20140922
+include("${CMAKE_CURRENT_LIST_DIR}/opentrack-policy.cmake" NO_POLICY_SCOPE)
+
+string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE)
+
+if(NOT CMAKE_INSTALL_PREFIX)
+ set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE STRING "" FORCE)
+endif()
+
SET(CMAKE_SYSTEM_NAME Windows)
SET(CMAKE_SYSTEM_VERSION 5)
+set(CMAKE_SYSROOT "/usr/i686-w64-mingw32")
+
+set(c "")
-# specify the cross compiler
+## specify the cross compiler
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
#set(p D:/cygwin64/opt/bin/)
#set(p "/mingw32/bin/")
@@ -15,7 +26,7 @@ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
set(c "${p}")
else()
set(p "")
- set(c "${p}i686-w64-mingw32-")
+ set(c "i686-w64-mingw32-")
set(e "")
endif()
@@ -23,13 +34,12 @@ SET(CMAKE_C_COMPILER ${c}gcc${e})
SET(CMAKE_CXX_COMPILER ${c}g++${e})
set(CMAKE_RC_COMPILER ${c}windres${e})
set(CMAKE_LINKER ${c}ld${e})
-set(CMAKE_AR ${c}gcc-ar${e} CACHE STRING "" FORCE)
-set(CMAKE_NM ${c}gcc-nm${e} CACHE STRING "" FORCE)
-set(CMAKE_RANLIB ${c}gcc-ranlib${e} CACHE STRING "" FORCE)
-set(CMAKE_OBJCOPY ${c}objcopy${e} CACHE STRING "" FORCE)
-set(CMAKE_OBJDUMP ${c}objdump${e} CACHE STRING "" FORCE)
-set(CMAKE_STRIP ${c}strip${e} CACHE STRING "" FORCE)
-
+set(CMAKE_AR ${c}gcc-ar${e})
+set(CMAKE_NM ${c}gcc-nm${e})
+set(CMAKE_RANLIB ${c}gcc-ranlib${e})
+set(CMAKE_OBJCOPY ${c}objcopy${e})
+set(CMAKE_OBJDUMP ${c}objdump${e})
+set(CMAKE_STRIP ${c}strip${e})
# search for programs in the host directories
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
@@ -38,30 +48,25 @@ SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# oldest CPU supported here is Northwood-based Pentium 4. -sh 20150811
-set(fpu "-ffast-math -mfpmath=both -mstackrealign -falign-functions=16 -falign-loops=16")
-set(cpu "-O3 -march=i686 -msse3 -mtune=skylake -frename-registers")
+set(fpu "-ffast-math -mfpmath=sse -mstackrealign")
+set(cpu "-O3 -msse3 -mtune=skylake")
#set(lto "-fno-lto -fno-use-linker-plugin -flto-compression-level=9 -flto-partition=balanced -fno-ipa-pta -fno-lto-odr-type-merging")
set(lto "")
-set(sections "-ffunction-sections -fdata-sections -s")
+set(sections "-ffunction-sections -fdata-sections")
set(cc "")
#set(cc "-fdump-statistics-details -fdump-ipa-cgraph")
-set(clang-warns "")
-if(CMAKE_COMPILER_IS_CLANG)
- set(clang-warns "-Wweak-vtables")
-endif()
-
set(noisy-warns "")
set(suggest-final "")
set(numerics "")
set(missing-override "")
if(WARNINGS_ENABLE)
if(WARNINGS_FINAL_SUGGESTIONS)
- set(suggest-final "-Wsuggest-final-types -Wsuggest-final-methods")
+ set(suggest-final "-Wsuggest-final-types")
endif()
if(WARNINGS_NUMERIC)
- set(numerics "-Wdouble-promotion -Wsign-compare")
+ set(numerics "-Wdouble-promotion")
endif()
if(WARNINGS_MISSING_OVERRIDE)
set(missing-override "-Wsuggest-override")
@@ -73,12 +78,12 @@ set(_CXX_WARNS "")
set(_C_WARNS "")
if(WARNINGS_ENABLE)
- set(usual-warns "-Wdelete-non-virtual-dtor -Wno-suggest-override -Wno-odr -Wno-attributes -Wcast-align")
+ set(usual-warns "-Wstrict-aliasing=3 -Wstrict-overflow=4 -Wdelete-non-virtual-dtor -Wno-odr -Wattributes")
set(_C_WARNS "-Wall -Wextra -Wpedantic -Wcast-align")
- set(_CXX_WARNS "${_C_WARNS} ${usual-warns} ${clang-warns} ${noisy-warns} ${missing-override}")
+ set(_CXX_WARNS "${_C_WARNS} ${usual-warns} ${noisy-warns} ${missing-override}")
endif()
-set(ccflags-common "-fvisibility=hidden -pipe -g3")
+set(ccflags-common "-pipe -g3")
set(_CXXFLAGS "${ccflags-common} ${_CXX_WARNS}")
set(_CFLAGS "${ccflags-common} -std=c11 ${_C_WARNS}")
set(_CFLAGS_RELEASE "${cpu} ${fpu} ${lto} ${sections} ${cc}")
@@ -88,8 +93,8 @@ set(_CXXFLAGS_DEBUG "${_CFLAGS_DEBUG}")
add_definitions(-DSTRSAFE_NO_DEPRECATE)
-set(_LDFLAGS "-Wl,--dynamicbase,--nxcompat,--as-needed")
-set(_LDFLAGS_RELEASE "-Wl,--gc-sections,--exclude-libs,ALL")
+#set(_LDFLAGS "-Wl,--dynamicbase,--nxcompat,--as-needed -Wl,--gc-sections,--exclude-libs,ALL")
+set(_LDFLAGS_RELEASE "")
set(_LDFLAGS_DEBUG "")
set(enable-val FALSE)
@@ -105,17 +110,12 @@ set(WARNINGS_MISSING_OVERRIDE FALSE CACHE BOOL "Emit very noisy warnings at comp
foreach(j C CXX)
foreach(i "" _DEBUG _RELEASE)
- set(CMAKE_${j}_FLAGS${i} "${_${j}FLAGS${i}} ${CMAKE_${j}_FLAGS${j}}")
- endforeach()
-endforeach()
-
-foreach(j "" _DEBUG _RELEASE)
- foreach(i MODULE EXE SHARED)
- set(CMAKE_${i}_LINKER_FLAGS${j} "${_LDFLAGS${j}} ${CMAKE_${i}_LINKER_FLAGS${j}}")
+ set(CMAKE_${j}_FLAGS${i} "${CMAKE_${j}_FLAGS${j}}")
endforeach()
endforeach()
-if(NOT CMAKE_BUILD_TYPE)
- set(CMAKE_BUILD_TYPE "RELEASE" CACHE STRING "" FORCE)
- set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "" FORCE)
-endif()
+#foreach(j "" _DEBUG _RELEASE)
+# foreach(i MODULE EXE SHARED)
+# set(CMAKE_${i}_LINKER_FLAGS${j} "${_LDFLAGS${j}} ${CMAKE_${i}_LINKER_FLAGS${j}}")
+# endforeach()
+#endforeach()
diff --git a/cmake/opentrack-clean-build-directory.cmake b/cmake/mrproper.cmake
index f531a6cb..1e8713ef 100644
--- a/cmake/opentrack-clean-build-directory.cmake
+++ b/cmake/mrproper.cmake
@@ -1,4 +1,4 @@
-list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
-include(opentrack-mrproper)
-
-cleanup_build_dir()
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
+include(opentrack-mrproper)
+
+cleanup_build_dir()
diff --git a/cmake/msvc-clang.cmake b/cmake/msvc-clang.cmake
deleted file mode 100644
index 419a3652..00000000
--- a/cmake/msvc-clang.cmake
+++ /dev/null
@@ -1,131 +0,0 @@
-# this file only serves as toolchain file when specified so explicitly
-# when building the software. from repository's root directory:
-# mkdir build && cmake -DCMAKE_TOOLCHAIN_FILE=$(pwd)/../cmake/msvc.cmake build/
-
-include("${CMAKE_CURRENT_LIST_DIR}/opentrack-policy.cmake")
-
-SET(CMAKE_SYSTEM_NAME Windows)
-SET(CMAKE_SYSTEM_VERSION 1)
-
-# search for programs in the host directories
-SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
-# don't poison with system compile-time data
-SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
-SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
-
-set(cc "")
-
-set(cc "${cc} -W3 -Wall -Wextra -Wno-unused-command-line-argument -Wno-missing-braces")
-set(cc "${cc} -Wno-inconsistent-missing-override")
-set(cc "${cc} -Wno-return-type-c-linkage")
-
-set(cc "${cc} -Xclang -std=c++14 -Xclang -fms-compatibility-version=1912 -fms-compatibility")
-
-set(cc "${cc} -U __clang__ -U__clang")
-
-set(cc "${cc} -O3")
-set(cc "${cc} -Xclang -O3 -Xclang -flto -Qvec -Oit -Oy -Ob2 -fp:fast -GS- -GF -GL -Gw -Gy -Gm -Zc:inline")
-set(cc "${cc} -Zo -FS -Zc:threadSafeInit -arch:SSE2 -D_HAS_EXCEPTIONS=0")
-set(cc "${cc} -bigobj")
-#set(cc "${cc} -Wno-unknown-argument -Wno-unknown-pragmas -Wno-invalid-noreturn")
-
-#set(CMAKE_CXX_SIMULATE_ID "MSVC" CACHE INTERNAL "" FORCE)
-#set(CMAKE_C_SIMULATE_ID "MSVC" CACHE INTERNAL "" FORCE)
-#set(CMAKE_CXX_SIMULATE_VERSION 19.0 CACHE INTERNAL "" FORCE)
-#set(CMAKE_C_SIMULATE_VERSION 19.0 CACHE INTERNAL "" FORCE)
-
-#set(CMAKE_INCLUDE_SYSTEM_FLAG_C "-Xclang -isystem")
-#set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX "-Xclang -isystem")
-
-#set(CMAKE_C_USE_RESPONSE_FILE_FOR_INCLUDES TRUE)
-#set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_INCLUDES TRUE)
-
-set(CMAKE_CXX_COMPILER "d:/llvm/msbuild-bin/cl.exe")
-set(CMAKE_C_COMPILER "d:/llvm/msbuild-bin/cl.exe")
-
-#set(CMAKE_CXX_COMPILER_ID "MSVC" CACHE INTERNAL "" FORCE)
-#set(CMAKE_C_COMPILER_ID "MSVC" CACHE INTERNAL "" FORCE)
-
-#set(CMAKE_CXX_COMPILER_VERSION "19.0" CACHE INTERNAL "" FORCE)
-#set(CMAKE_C_COMPILER_VERSION "19.0" CACHE INTERNAL "" FORCE)
-
-#set(CMAKE_VS_PLATFORM_TOOLSET "v150_clang_4_0")
-
-set(CMAKE_CXX_FLAGS_RELEASE_INIT " ")
-set(CMAKE_C_FLAGS_RELEASE_INIT " ")
-
-set(CMAKE_CXX_STANDARD_REQUIRED FALSE)
-set(CMAKE_CXX_EXTENSIONS FALSE)
-
-set(warns_ "")
-
-set(warns-disable 4530 4577 4789 4244 4702 4530 4244 4127 4458 4456 4251)
-
-foreach(i ${warns-disable})
- set(warns_ "${warns_} -wd${i}")
-endforeach()
-if(CMAKE_PROJECT_NAME STREQUAL "opentrack")
- #C4263 - member function does not override any base class virtual member function
- #C4264 - no override available for virtual member function from base class, function is hidden
- #C4265 - class has virtual functions, but destructor is not virtual
- #C4266 - no override available for virtual member function from base type, function is hidden
- #C4928 - illegal copy-initialization, more than one user-defined conversion has been implicitly applied
-
- set(warns 4263 4264 4266 4928)
- set(warns-noerr 4265)
-
- foreach(i ${warns})
- set(warns_ "${warns_} -w1${i} -we${i}")
- endforeach()
-
- foreach(i ${warns-noerr})
- set(warns_ "${warns_} -w1${i}")
- endforeach()
- set(cc "${cc} -GR-")
-endif()
-
-set(base-cflags "${warns_} -MT -Zi -cgthreads8 -W4")
-
-set(_CFLAGS "${base-cflags}")
-set(_CXXFLAGS "${base-cflags}")
-set(_CFLAGS_RELEASE "${cc}")
-set(_CFLAGS_DEBUG "-GS -sdl -Gs -guard:cf")
-set(_CXXFLAGS_RELEASE "${cc}")
-set(_CXXFLAGS_DEBUG "${_CFLAGS_DEBUG}")
-
-set(_LDFLAGS_COMMON "-ignore:4217 -ignore:4221")
-set(_LDFLAGS "-machine:X86 -DEBUG ${_LDFLAGS_COMMON}")
-set(_LDFLAGS_RELEASE "-OPT:REF -OPT:ICF=10")
-set(_LDFLAGS_DEBUG "")
-
-set(_LDFLAGS_STATIC "-machine:X86 ${_LDFLAGS_COMMON}")
-set(_LDFLAGS_STATIC_RELEASE "")
-set(_LDFLAGS_STATIC_DEBUG "")
-
-foreach(j C CXX)
- foreach(i "" _DEBUG _RELEASE)
- set(CMAKE_${j}_FLAGS${i} "${CMAKE_${j}_FLAGS${i}} ${_${j}FLAGS${i}}")
- endforeach()
- set(CMAKE_${j}_FLAGS "${CMAKE_${j}_FLAGS} ${_${j}FLAGS}")
-endforeach()
-
-foreach(j "" _DEBUG _RELEASE)
- foreach(i MODULE EXE SHARED)
- set(CMAKE_${i}_LINKER_FLAGS${j} "${_LDFLAGS${j}} ${CMAKE_${i}_LINKER_FLAGS${j}}")
- endforeach()
-endforeach()
-
-set(CMAKE_STATIC_LINKER_FLAGS "${_LDFLAGS_STATIC} ${CMAKE_STATIC_LINKER_FLAGS}")
-set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${_LDFLAGS_STATIC_RELEASE} ${CMAKE_STATIC_LINKER_FLAGS_RELEASE}")
-set(CMAKE_STATIC_LINKER_FLAGS_DEBUG "${_LDFLAGS_STATIC_DEBUG} ${CMAKE_STATIC_LINKER_FLAGS_DEBUG}")
-
-set(CMAKE_RC_FLAGS "-nologo -DWIN32")
-
-if(NOT CMAKE_INSTALL_PREFIX)
- set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "" FORCE)
-endif()
-
-if(NOT CMAKE_BUILD_TYPE)
- set(CMAKE_BUILD_TYPE "RELEASE" CACHE STRING "" FORCE)
-endif()
-
diff --git a/cmake/msvc.cmake b/cmake/msvc.cmake
index d168bad4..b390c120 100644
--- a/cmake/msvc.cmake
+++ b/cmake/msvc.cmake
@@ -3,7 +3,24 @@
# mkdir build && cmake -DCMAKE_TOOLCHAIN_FILE=$(pwd)/../cmake/msvc.cmake build/
SET(CMAKE_SYSTEM_NAME Windows)
-SET(CMAKE_SYSTEM_VERSION 6.0)
+SET(CMAKE_SYSTEM_VERSION 5.01)
+if(opentrack-64bit)
+ set(CMAKE_SYSTEM_PROCESSOR AMD64)
+else()
+ set(CMAKE_SYSTEM_PROCESSOR x86)
+endif()
+
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT OR CMAKE_INSTALL_PREFIX STREQUAL "")
+ set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "" FORCE)
+endif()
+
+if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/opentrack-policy.cmake")
+ include("${CMAKE_CURRENT_LIST_DIR}/opentrack-policy.cmake" NO_POLICY_SCOPE)
+endif()
+
+#set(CMAKE_GENERATOR Ninja)
+#set(CMAKE_MAKE_PROGRAM ninja.exe)
+set(CMAKE_ASM_NASM_COMPILER nasm.exe)
# search for programs in the host directories
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
@@ -11,93 +28,178 @@ SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
-set(cc "")
-# oldest CPU supported here is Northwood-based Pentium 4. -sh 20150811
-set(cc "${cc} -O2 -O2it -Oy- -Ob2 -fp:fast -GS- -GF -GL -Gw -Gy -Gm")
-set(cc "${cc} -Zo -FS -arch:SSE2 -D_HAS_EXCEPTIONS=0")
-set(cc "${cc} -bigobj -cgthreads1")
-set(cc "${cc} -Zc:inline -Zc:rvalueCast")
-#set(cc "${cc} -Qvec-report:1")
-set(warns_ "")
+#add_definitions(-D_ITERATOR_DEBUG_LEVEL=0)
+#add_compile_options(-Qvec-report:2)
+#add_compile_options(-d2cgsummary)
+add_definitions(-D_HAS_EXCEPTIONS=0)
+
+if(DEFINED CMAKE_TOOLCHAIN_FILE)
+ # ignore cmake warning: Manually-specified variable not used by the project
+ set(CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}")
+endif()
-set(warns-disable 4530 4577 4789 4244 4702 4530 4244 4127 4458 4456 4251 4100)
+include("${CMAKE_CURRENT_LIST_DIR}/opentrack-policy.cmake" NO_POLICY_SCOPE)
+set(CMAKE_POLICY_DEFAULT_CMP0069 NEW CACHE INTERNAL "" FORCE)
+#set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
-foreach(i ${warns-disable})
- set(warns_ "${warns_} -wd${i}")
-endforeach()
+set(CMAKE_C_EXTENSIONS FALSE)
+set(CMAKE_CXX_EXTENSIONS FALSE)
-foreach(k CMP0020 CMP0022 CMP0058 CMP0028 CMP0042 CMP0063 CMP0053 CMP0011 CMP0054 CMP0012)
- if(POLICY ${k})
- cmake_policy(SET ${k} NEW)
+set(CMAKE_CROSSCOMPILING 0)
+
+function(sets type)
+ set(i 0)
+ list(LENGTH ARGN max)
+ math(EXPR foo "${max} % 2")
+ if(NOT foo EQUAL 0)
+ message(FATAL_ERROR "argument count not even")
endif()
-endforeach()
+ while(i LESS max)
+ list(GET ARGN "${i}" name)
+ math(EXPR i "${i} + 1")
+ list(GET ARGN "${i}" value)
+ math(EXPR i "${i} + 1")
+ set(${name} "${value}" CACHE "${type}" "" FORCE)
+ endwhile()
+endfunction()
if(CMAKE_PROJECT_NAME STREQUAL "opentrack")
- #C4263 - member function does not override any base class virtual member function
- #C4264 - no override available for virtual member function from base class, function is hidden
- #C4265 - class has virtual functions, but destructor is not virtual
- #C4266 - no override available for virtual member function from base type, function is hidden
- #C4928 - illegal copy-initialization, more than one user-defined conversion has been implicitly applied
+ #include("${CMAKE_CURRENT_LIST_DIR}/opentrack-policy.cmake" NO_POLICY_SCOPE)
- set(warns 4263 4264 4266 4928)
- set(warns-noerr 4265)
+ add_compile_options("-W4")
- foreach(i ${warns})
- set(warns_ "${warns_} -w1${i} -we${i}")
- endforeach()
+ # C4265: class has virtual functions, but destructor is not virtual
+ # C4005: macro redefinition
+ set(warns-error 4265 4005)
- foreach(i ${warns-noerr})
- set(warns_ "${warns_} -w1${i}")
+ foreach(i ${warns-error})
+ add_compile_options(-w1${i} -we${i})
endforeach()
- set(cc "${cc} -GR-")
endif()
-set(base-cflags "${warns_} -MT -Zi -W4")
-#set(base-cflags "${base-cflags} -d2cgsummary")
+if(CMAKE_PROJECT_NAME STREQUAL "QtBase")
+ set(QT_BUILD_TOOLS_WHEN_CROSSCOMPILING TRUE CACHE BOOL "" FORCE)
+ set(QT_DEBUG_OPTIMIZATION_FLAGS TRUE CACHE BOOL "" FORCE)
+ set(QT_USE_DEFAULT_CMAKE_OPTIMIZATION_FLAGS TRUE CACHE BOOL "" FORCE)
-set(_CFLAGS "${base-cflags}")
-set(_CXXFLAGS "${base-cflags}")
-set(_CFLAGS_RELEASE "${cc}")
-set(_CFLAGS_DEBUG "-GS -sdl -Gs -guard:cf")
-set(_CXXFLAGS_RELEASE "${cc}")
-set(_CXXFLAGS_DEBUG "${_CFLAGS_DEBUG}")
+ set(FEATURE_debug OFF)
+ set(FEATURE_debug_and_release OFF)
+ set(FEATURE_force_debug_info ON)
+ set(FEATURE_ltcg ON)
+ set(FEATURE_shared ON)
+endif()
-set(_LDFLAGS "-machine:X86 -DEBUG")
+if(CMAKE_PROJECT_NAME STREQUAL "OpenCV")
+ set(PARALLEL_ENABLE_PLUGINS FALSE)
+ set(HIGHGUI_ENABLE_PLUGINS FALSE)
+ set(VIDEOIO_ENABLE_PLUGINS FALSE)
-#set(_ltcg "-LTCG")
-set(_ltcg "-LTCG:INCREMENTAL")
+ set(OPENCV_SKIP_MSVC_EXCEPTIONS_FLAG TRUE)
+ set(OPENCV_DISABLE_THREAD_SUPPORT TRUE)
-set(_LDFLAGS_RELEASE "-OPT:REF,ICF -cgthreads:1 ${_ltcg}")
-set(_LDFLAGS_DEBUG "")
+ set(ENABLE_FAST_MATH ON)
+ set(BUILD_TESTS OFF)
+ set(BUILD_PERF_TESTS OFF)
+ set(BUILD_opencv_apps OFF)
+ set(BUILD_opencv_gapi OFF)
+endif()
-set(_LDFLAGS_STATIC "-machine:X86 -WX")
-set(_LDFLAGS_STATIC_RELEASE "${_ltcg}")
-set(_LDFLAGS_STATIC_DEBUG "")
+if(CMAKE_PROJECT_NAME STREQUAL "TestOscpack")
+ add_compile_definitions(OSC_HOST_LITTLE_ENDIAN)
+endif()
-# debugging build times
-#set(_CXXFLAGS "${_CXXFLAGS} -Bt+")
-set(_LDFLAGS "${_LDFLAGS} -time")
+set(opentrack-simd "SSE2")
+
+if(CMAKE_PROJECT_NAME STREQUAL "onnxruntime")
+ sets(BOOL
+ ONNX_USE_MSVC_STATIC_RUNTIME OFF
+ protobuf_MSVC_STATIC_RUNTIME OFF
+ onnxruntime_USE_AVX OFF
+ onnxruntime_USE_AVX2 OFF
+ onnxruntime_USE_AVX512 OFF
+ onnxruntime_BUILD_BENCHMARKS OFF
+ onnxruntime_BUILD_FOR_NATIVE_MACHINE OFF
+ onnxruntime_BUILD_SHARED_LIB ON
+ onnxruntime_BUILD_UNIT_TESTS OFF
+ protobuf_BUILD_EXAMPLES OFF
+ protobuf_BUILD_SHARED_LIBS OFF
+ ONNX_BUILD_BENCHMARKS OFF
+ ONNX_BUILD_TESTS OFF
+ ONNX_DISABLE_EXCEPTIONS OFF # important!
+ ONNX_GEN_PB_TYPE_STUBS OFF
+ onnxruntime_DISABLE_CONTRIB_OPS ON
+ BUILD_TESTING OFF
+ )
+ if(opentrack-64bit)
+ sets(BOOL
+ onnxruntime_USE_AVX ON
+ )
+ endif()
+endif()
-set(CMAKE_RC_FLAGS "/nologo -DWIN32")
+if(opentrack-64bit)
+ set(opentrack-simd "AVX")
+endif()
-set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "" FORCE)
-set(CMAKE_BUILD_TYPE "RELEASE" CACHE STRING "" FORCE)
+set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
+add_compile_options(-MD)
+
+add_link_options(-cgthreads:1)
+
+set(_CFLAGS "-diagnostics:caret -Zc:inline -Zc:preprocessor -wd4117 -Zi -Zf -Zo -bigobj -cgthreads1 -vd0 -permissive-")
+#if(NOT opentrack-no-static-crt)
+# set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded" CACHE INTERNAL "" FORCE)
+#else()
+# set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE INTERNAL "" FORCE)
+#endif()
+set(_CXXFLAGS "${_CFLAGS} -Zc:throwingNew -Zc:lambda")
+set(_CFLAGS_RELEASE "-O2 -Oit -Oy- -Ob3 -fp:fast -GS- -GF -GL -Gw -Gy")
+if(NOT opentrack-simd STREQUAL "")
+ set(_CFLAGS_RELEASE "${_CFLAGS_RELEASE} -arch:${opentrack-simd}")
+endif()
+set(_CFLAGS_DEBUG "-guard:cf -MTd -Gs0 -RTCs")
+set(_CXXFLAGS_RELEASE "${_CFLAGS_RELEASE}")
+set(_CXXFLAGS_DEBUG "${_CFLAGS_DEBUG}")
-set(CMAKE_STATIC_LINKER_FLAGS "${_LDFLAGS_STATIC}" CACHE STRING "" FORCE)
-set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${_LDFLAGS_STATIC_RELEASE}" CACHE STRING "" FORCE)
-set(CMAKE_STATIC_LINKER_FLAGS_DEBUG "${_LDFLAGS_STATIC_DEBUG}" CACHE STRING "" FORCE)
+set(_LDFLAGS "-WX")
+set(_LDFLAGS_RELEASE "-OPT:REF,ICF=10 -LTCG -DEBUG:FULL")
+set(_LDFLAGS_DEBUG "-DEBUG:FULL")
-foreach(j C CXX)
- foreach(i "" _DEBUG _RELEASE)
- set(CMAKE_${j}_FLAGS${i} "${_${j}FLAGS${i}}" CACHE STRING "" FORCE)
- endforeach()
+set(_LDFLAGS_STATIC "-WX")
+set(_LDFLAGS_STATIC_RELEASE "-LTCG")
+set(_LDFLAGS_STATIC_DEBUG "")
+
+set(CMAKE_BUILD_TYPE_INIT "RELEASE" CACHE INTERNAL "")
+
+string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE)
+set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "" FORCE)
+if (NOT CMAKE_BUILD_TYPE STREQUAL "RELEASE" AND NOT CMAKE_BUILD_TYPE STREQUAL "DEBUG")
+ set(CMAKE_BUILD_TYPE "RELEASE" CACHE STRING "" FORCE)
+endif()
+set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS RELEASE DEBUG)
+
+foreach(k "" "_${CMAKE_BUILD_TYPE}")
+ set("FLAGS_CXX${k}" "" CACHE STRING "More CMAKE_CXX_FLAGS${k}")
+ #set("FLAGS_C${k}" "" CACHE STRING "More CMAKE_C_FLAGS${k} (almost never used)")
+ set("FLAGS_LD${k}" "" CACHE STRING "More CMAKE_(SHARED|EXE|MODULE)_LINKER_FLAGS${k}")
+ set("FLAGS_ARCHIVE${k}" "" CACHE STRING "More CMAKE_STATIC_LINKER_FLAGS${k}")
endforeach()
+foreach(k "" _DEBUG _RELEASE)
+ #set(CMAKE_STATIC_LINKER_FLAGS${k} "${CMAKE_STATIC_LINKER_FLAGS${k}} ${_LDFLAGS_STATIC${k}}")
+ set(CMAKE_STATIC_LINKER_FLAGS${k} "${_LDFLAGS_STATIC${k}} ${FLAGS_ARCHIVE${k}}" CACHE STRING "" FORCE)
+endforeach()
foreach(j "" _DEBUG _RELEASE)
foreach(i MODULE EXE SHARED)
- set(CMAKE_${i}_LINKER_FLAGS${j} "${_LDFLAGS${j}}" CACHE STRING "" FORCE)
+ #set(CMAKE_${i}_LINKER_FLAGS${j} "${CMAKE_${i}_LINKER_FLAGS${j}} ${_LDFLAGS${j}}")
+ set(CMAKE_${i}_LINKER_FLAGS${j} "${_LDFLAGS${j}} ${FLAGS_LD${j}}" CACHE STRING "" FORCE)
endforeach()
endforeach()
-include("${CMAKE_CURRENT_LIST_DIR}/opentrack-policy.cmake")
+foreach(j C CXX)
+ foreach(i "" _DEBUG _RELEASE)
+ #set(CMAKE_${j}_FLAGS${i} "${CMAKE_${j}_FLAGS${i}} ${_${j}FLAGS${i}}")
+ set(CMAKE_${j}_FLAGS${i} "${_${j}FLAGS${i}} ${FLAGS_${j}${i}}" CACHE STRING "" FORCE)
+ endforeach()
+endforeach()
diff --git a/cmake/opentrack-boilerplate.cmake b/cmake/opentrack-boilerplate.cmake
index bea2b47a..e325cd6a 100644
--- a/cmake/opentrack-boilerplate.cmake
+++ b/cmake/opentrack-boilerplate.cmake
@@ -1,53 +1,68 @@
+include_guard(GLOBAL)
+
set(opentrack-perms-file WORLD_READ OWNER_WRITE OWNER_READ GROUP_READ)
set(opentrack-perms-dir WORLD_READ WORLD_EXECUTE OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE)
set(opentrack-perms-exec "${opentrack-perms-dir}")
set(new-hier-path "#pragma once
-#ifndef OPENTRACK_NO_QT_PATH
-# include <QCoreApplication>
-# include <QString>
+#ifdef QT_CORE_LIB
# include \"compat/base-path.hpp\"
#endif
-#define OPENTRACK_LIBRARY_PATH \"${opentrack-hier-path}\"
-#define OPENTRACK_DOC_PATH \"${opentrack-hier-doc}\"
-#define OPENTRACK_CONTRIB_PATH \"${opentrack-hier-doc}contrib/\"
-#define OPENTRACK_I18N_PATH \"${opentrack-i18n-path}\"
+
+#if defined __APPLE__
+# define OPENTRACK_LIBRARY_EXTENSION \"dylib\"
+#elif defined _WIN32
+# define OPENTRACK_LIBRARY_EXTENSION \"dll\"
+#else
+# define OPENTRACK_LIBRARY_EXTENSION \"so\"
+#endif
+
+#define OPENTRACK_LIBRARY_PREFIX \"\"
+#define OPENTRACK_LIBRARY_PATH \"${opentrack-runtime-libexec}\"
+#define OPENTRACK_DOC_PATH \"${opentrack-runtime-doc}\"
+#define OPENTRACK_CONTRIB_PATH \"${opentrack-runtime-doc}contrib/\"
+#define OPENTRACK_I18N_PATH \"${opentrack-runtime-i18n}\"
")
-include_directories("${CMAKE_BINARY_DIR}")
+function(otr_write_library_paths)
+ set(hier-path-filename "${CMAKE_BINARY_DIR}/opentrack-library-path.hxx")
+ set(orig-hier-path "")
+ if(EXISTS "${hier-path-filename}")
+ file(READ ${hier-path-filename} orig-hier-path)
+ endif()
+ if(NOT (orig-hier-path STREQUAL new-hier-path))
+ file(WRITE "${hier-path-filename}" "${new-hier-path}")
+ endif()
+endfunction()
-set(hier-path-filename "${CMAKE_BINARY_DIR}/__opentrack-library-path.h")
-set(orig-hier-path "")
-if(EXISTS "${hier-path-filename}")
- file(READ ${hier-path-filename} orig-hier-path)
-endif()
-if(NOT (orig-hier-path STREQUAL new-hier-path))
- file(WRITE "${hier-path-filename}" "${new-hier-path}")
-endif()
+otr_write_library_paths()
function(otr_glob_sources var)
set(basedir "${CMAKE_CURRENT_SOURCE_DIR}")
- foreach(dir . ${ARGN})
+ foreach(i ui rc res cc cxx hh all)
+ set(${var}-${i} "")
+ endforeach()
+ foreach(dir ${ARGN})
set(dir "${basedir}/${dir}")
- file(GLOB ${var}-cxx ${dir}/*.cpp)
- file(GLOB ${var}-cc ${dir}/*.c)
- file(GLOB ${var}-hh ${dir}/*.h ${dir}/*.hpp)
- file(GLOB ${var}-res ${dir}/*.rc)
- foreach(f ${var}-res)
+ file(GLOB cxx CONFIGURE_DEPENDS "${dir}/*.cpp")
+ file(GLOB cc CONFIGURE_DEPENDS "${dir}/*.c")
+ file(GLOB hh CONFIGURE_DEPENDS "${dir}/*.h" "${dir}/*.hpp" "${dir}/*.inc")
+ file(GLOB res CONFIGURE_DEPENDS "${dir}/*.rc")
+ foreach(f res)
set_source_files_properties(${f} PROPERTIES LANGUAGE RC)
endforeach()
- file(GLOB ${var}-ui ${dir}/*.ui)
- file(GLOB ${var}-rc ${dir}/*.qrc)
- set(${var}-all ${${var}-cc} ${${var}-cxx} ${${var}-hh} ${${var}-rc} ${${var}-res})
+ file(GLOB ui CONFIGURE_DEPENDS "${dir}/*.ui")
+ file(GLOB rc CONFIGURE_DEPENDS "${dir}/*.qrc")
+ set(all ${cc} ${cxx} ${hh} ${res})
foreach(i ui rc res cc cxx hh all)
- set(${var}-${i} "${${var}-${i}}" PARENT_SCOPE)
+ set(${var}-${i} "${${var}-${i}}" ${${i}} PARENT_SCOPE)
endforeach()
endforeach()
endfunction()
function(otr_fixup_subsystem n)
- otr_find_msvc_editbin(editbin-pathname)
if(MSVC)
+ otr_find_msvc_editbin(editbin-pathname)
set(subsystem WINDOWS)
get_property(type TARGET "${n}" PROPERTY TYPE)
if (NOT type STREQUAL "STATIC_LIBRARY")
@@ -61,27 +76,16 @@ endfunction()
function(otr_compat target)
if(NOT MSVC)
- set_property(SOURCE ${${target}-moc} APPEND_STRING PROPERTY COMPILE_FLAGS "-w -Wno-error ")
+ set_property(SOURCE ${${target}-moc} APPEND PROPERTY COMPILE_OPTIONS "-w;-Wno-error")
endif()
- get_property(type TARGET "${n}" PROPERTY TYPE)
- if (".${TYPE}" STREQUAL ".EXECUTABLE")
- otr_fixup_subsystem(${target})
+ if(UNIX) # no-op on OSX
+ target_link_libraries(${target} m)
endif()
- if(CMAKE_COMPILER_IS_GNUCXX AND NOT MSVC)
- # gives incorrect result
- #get_property(linker-lang TARGET ${target} PROPERTY LINKER_LANGUAGE)
-
- set_property(TARGET ${target} APPEND_STRING PROPERTY COMPILE_FLAGS "-fvisibility=hidden ")
-
- if (NOT ".${${target}-cxx}" STREQUAL ".") # not a C only target
- set_property(TARGET ${target} APPEND_STRING PROPERTY COMPILE_FLAGS "-fuse-cxa-atexit ")
- endif()
-
- if(NOT APPLE)
- set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS "-Wl,--as-needed ")
- endif()
+ get_property(type TARGET "${n}" PROPERTY TYPE)
+ if (type STREQUAL "EXECUTABLE")
+ otr_fixup_subsystem(${target})
endif()
endfunction()
@@ -96,42 +100,30 @@ endfunction()
function(otr_find_msvc_editbin varname)
if(MSVC)
- # on .sln generator we have no editbin in path
- is_sln_generator(is-msvc)
- if(is-msvc)
- # this is bad but what can we do? searching for vcvarsall.bat
- # would be easier, but then we'd have to differentiate x86/amd64
- # cross tools, etc. which is a can of worms and if/else branches.
- get_filename_component(linker-dir "${CMAKE_LINKER}" DIRECTORY)
- find_file(opentrack_editbin-executable "editbin.exe" "${linker-dir}" "${linker-dir}/.." "${linker-dir}/../..")
- otr_escape_string("${editbin-executable}" editbin-executable)
- else()
- set(opentrack_editbin-executable "editbin")
- endif()
- mark_as_advanced(FORCE opentrack_editbin-executable)
- set("${varname}" "${opentrack_editbin-executable}" PARENT_SCOPE)
+ get_filename_component(linker-dir "${CMAKE_LINKER}" DIRECTORY)
+ set("${varname}" "${linker-dir}/editbin.exe" PARENT_SCOPE)
+ else()
+ set("${varname}" "editbin" PARENT_SCOPE)
endif()
endfunction()
function(otr_install_pdb_current_project target)
if(MSVC)
- install(FILES "$<TARGET_PDB_FILE:${target}>" DESTINATION "${opentrack-hier-debug}" PERMISSIONS ${opentrack-perms-file})
+ install(FILES "$<TARGET_PDB_FILE:${target}>" DESTINATION "${opentrack-debug}" PERMISSIONS ${opentrack-perms-file})
endif()
endfunction()
-include(CMakeParseArguments)
-
function(otr_module n_)
message(STATUS "module ${n_}")
cmake_parse_arguments(arg
- "STATIC;NO-COMPAT;BIN;EXECUTABLE;NO-QT;WIN32-CONSOLE;NO-INSTALL;RELINK"
+ "STATIC;NO-COMPAT;BIN;EXECUTABLE;NO-QT;NO-I18N;WIN32-CONSOLE;NO-INSTALL;RELINK"
"LINK;COMPILE"
"SOURCES;SUBDIRS"
${ARGN}
)
if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "")
- message(FATAL_ERROR "otr_module bad formals: ${arg_UNPARSED_ARGUMENTS}")
+ message(FATAL_ERROR "otr_module(${n_}) bad formals: ${arg_UNPARSED_ARGUMENTS}")
endif()
set(n "opentrack-${n_}")
@@ -139,7 +131,7 @@ function(otr_module n_)
if(NOT arg_SUBDIRS)
otr_glob_sources(${n} .)
else()
- otr_glob_sources(${n} ${arg_SUBDIRS})
+ otr_glob_sources(${n} . ${arg_SUBDIRS})
endif()
list(APPEND ${n}-all ${arg_SOURCES})
@@ -148,30 +140,44 @@ function(otr_module n_)
otr_qt(${n})
else()
set(arg_NO-COMPAT TRUE)
- endif()
-
- if(NOT WIN32)
- set(subsys "")
- elseif(arg_WIN32-CONSOLE)
- set(subsys "")
- else()
- set(subsys "WIN32")
+ set(arg_NO-I18N TRUE)
endif()
if(arg_EXECUTABLE)
+ if (APPLE)
+ set(subsys "MACOSX_BUNDLE")
+ elseif(NOT WIN32)
+ set(subsys "")
+ elseif(arg_WIN32-CONSOLE)
+ set(subsys "")
+ else()
+ set(subsys "WIN32")
+ endif()
+
add_executable(${n} ${subsys} "${${n}-all}")
+ set_target_properties(${n} PROPERTIES
+ SUFFIX "${opentrack-binary-suffix}"
+ OUTPUT_NAME "${n_}"
+ PREFIX "")
+ if(MSVC)
+ target_compile_options(${n} PRIVATE -GA)
+ endif()
else()
set(link-mode SHARED)
if (arg_STATIC)
set(link-mode STATIC)
endif()
add_library(${n} ${link-mode} "${${n}-all}")
- set_property(TARGET "${n}" PROPERTY PREFIX "lib")
+ set_property(TARGET "${n}" PROPERTY PREFIX "")
endif()
- set_property(SOURCE ${${n}-moc} ${${n}-uih} ${${n}-rcc} PROPERTY GENERATED TRUE)
+ if(NOT arg_NO-QT)
+ otr_qt2("${n}")
+ endif()
- if(NOT arg_RELINK)
+ set(self "${n}" PARENT_SCOPE)
+
+ if(NOT arg_RELINK AND FALSE)
set_property(TARGET ${n} PROPERTY LINK_DEPENDS_NO_SHARED TRUE)
else()
set_property(TARGET ${n} PROPERTY LINK_DEPENDS_NO_SHARED FALSE)
@@ -185,27 +191,51 @@ function(otr_module n_)
target_link_libraries(${n} opentrack-api opentrack-options opentrack-compat)
endif()
- target_compile_definitions("${n}" PRIVATE "-DOTR_MODULE_NAME=\"${n_}\"")
string(REPLACE "-" "_" build-n ${n_})
string(TOUPPER "${build-n}" build-n)
- set_property(TARGET ${n} PROPERTY DEFINE_SYMBOL "BUILD_${build-n}")
-
- get_property(ident GLOBAL PROPERTY opentrack-ident)
- if (".${ident}" STREQUAL ".")
- message(FATAL_ERROR "must set global property `opentrack-ident' in `opentrack-variant.cmake'")
- endif()
- set_property(TARGET ${n} APPEND PROPERTY COMPILE_DEFINITIONS "OPENTRACK_ORG=\"${ident}\"")
+ target_compile_definitions(${n} PRIVATE "BUILD_${build-n}")
+ include(opentrack-org)
if(arg_STATIC)
set(arg_NO-INSTALL TRUE)
endif()
+ otr_compat(${n})
+
+ if(CMAKE_COMPILER_IS_CLANGXX AND (arg_EXECUTABLE OR NOT arg_BIN))
+ set(opts
+ weak-vtables
+ header-hygiene
+ )
+ foreach(k ${opts})
+ target_compile_options(${n} PRIVATE "-Wno-${k}")
+ endforeach()
+ endif()
+
if(NOT arg_NO-INSTALL)
- if(arg_BIN AND WIN32)
- install(TARGETS "${n}" RUNTIME DESTINATION . PERMISSIONS ${opentrack-perms-exec})
+ # Librarys/executable
+ if(arg_BIN)
+ if (APPLE)
+ install(TARGETS "${n}"
+ RUNTIME DESTINATION "${opentrack-bin}"
+ BUNDLE DESTINATION "${opentrack-bin}"
+ LIBRARY DESTINATION "${opentrack-bin}/Library"
+ RESOURCE DESTINATION "${opentrack-bin}/opentrack.app/Resource"
+ PERMISSIONS ${opentrack-perms-exec})
+ else()
+ install(TARGETS "${n}"
+ RUNTIME DESTINATION "${opentrack-bin}"
+ LIBRARY DESTINATION "${opentrack-libexec}"
+ PERMISSIONS ${opentrack-perms-exec})
+ endif()
else()
- install(TARGETS "${n}" ${opentrack-hier-str} PERMISSIONS ${opentrack-perms-exec})
+ # Plugins
+ install(TARGETS "${n}"
+ RUNTIME DESTINATION "${opentrack-libexec}"
+ LIBRARY DESTINATION "${opentrack-libexec}"
+ PERMISSIONS ${opentrack-perms-exec})
endif()
+
if(MSVC)
set(opentrack_install-debug-info FALSE CACHE BOOL "Whether to build and install debug info at install time")
if(opentrack_install-debug-info)
@@ -214,18 +244,14 @@ function(otr_module n_)
endif()
endif()
- otr_compat(${n})
- if(NOT arg_NO-QT)
+ if(NOT arg_NO-I18N)
otr_i18n_for_target_directory(${n_})
-
- foreach(k ${opentrack_all-translations})
- set(i18n-target "i18n-lang-${k}-module-${n_}")
- add_dependencies("${n}" "${i18n-target}")
- endforeach()
endif()
- set_property(GLOBAL APPEND PROPERTY opentrack-all-modules "${n}")
+ set_property(GLOBAL APPEND PROPERTY opentrack-all-modules "${n_}")
set_property(GLOBAL APPEND PROPERTY opentrack-all-source-dirs "${CMAKE_CURRENT_SOURCE_DIR}")
+
+ #make_directory("${CMAKE_CURRENT_BINARY_DIR}")
endfunction()
function(otr_add_target_dirs var)
@@ -234,7 +260,42 @@ function(otr_add_target_dirs var)
list(APPEND globs "${k}/CMakeLists.txt")
endforeach()
set(projects "")
- file(GLOB projects ${globs})
+ file(GLOB projects CONFIGURE_DEPENDS ${globs})
list(SORT projects)
set("${var}" "${projects}" PARENT_SCOPE)
endfunction()
+
+function(otr_pdb_for_dll varname path)
+ set("${varname}" "" PARENT_SCOPE)
+ get_filename_component(dir "${path}" DIRECTORY)
+ get_filename_component(name-only "${path}" NAME_WE)
+ set(pdb-path "${dir}/${name-only}.pdb")
+ if(EXISTS "${pdb-path}")
+ set("${varname}" "${pdb-path}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+function(otr_install_lib target dest)
+ if(WIN32)
+ string(FIND "${target}" "/" idx)
+ if(idx EQUAL -1)
+ get_property(path TARGET "${target}" PROPERTY "LOCATION_${CMAKE_BUILD_TYPE}")
+ else()
+ set(path "${target}")
+ endif()
+ if(path STREQUAL "")
+ message(FATAL_ERROR "Can't find ${target}")
+ endif()
+ string(TOLOWER "${path}" path_)
+ if(NOT path_ MATCHES "\\.(a|lib)$")
+ if(MSVC AND opentrack_install-debug-info)
+ set(pdb-path "")
+ otr_pdb_for_dll(pdb-path "${path}")
+ if(pdb-path)
+ install(FILES "${pdb-path}" DESTINATION "${opentrack-debug}" PERMISSIONS ${opentrack-perms-exec})
+ endif()
+ endif()
+ install(FILES "${path}" DESTINATION "${dest}" PERMISSIONS ${opentrack-perms-exec})
+ endif()
+ endif()
+endfunction()
diff --git a/cmake/opentrack-check-build-directory.cmake b/cmake/opentrack-check-build-directory.cmake
deleted file mode 100644
index a9351b6a..00000000
--- a/cmake/opentrack-check-build-directory.cmake
+++ /dev/null
@@ -1,7 +0,0 @@
-get_filename_component(_abs_src_path ${CMAKE_SOURCE_DIR} REALPATH)
-string(TOLOWER ${_abs_src_path} _abs_src_path)
-get_filename_component(_abs_build_path ${CMAKE_BINARY_DIR} REALPATH)
-string(TOLOWER ${_abs_build_path} _abs_build_path)
-if(_abs_src_path STREQUAL _abs_build_path)
- message(FATAL_ERROR "source directory has to be different than build directory")
-endif()
diff --git a/cmake/opentrack-hier.cmake b/cmake/opentrack-hier.cmake
index 7a0659c6..bb1a435d 100644
--- a/cmake/opentrack-hier.cmake
+++ b/cmake/opentrack-hier.cmake
@@ -1,61 +1,54 @@
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
-# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
-# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
-# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
-# PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
-# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-set(opentrack-install-rpath "")
-if(APPLE)
- set(opentrack-hier-pfx ".")
- set(opentrack-hier-path "/") # MUST HAVE A TRAILING BACKSLASH
- set(opentrack-hier-doc "/") # MUST HAVE A TRAILING BACKSLASH
- set(opentrack-hier-str RUNTIME DESTINATION . LIBRARY DESTINATION .)
- set(opentrack-doc-pfx "./doc")
- set(opentrack-doc-src-pfx "./source-code")
- set(opentrack-i18n-pfx "./i18n")
- set(opentrack-i18n-path "./i18n")
-elseif(WIN32)
- set(opentrack-hier-pfx "./modules")
- set(opentrack-hier-path "/modules/") # MUST HAVE A TRAILING BACKSLASH
- set(opentrack-hier-doc "/doc/") # MUST HAVE A TRAILING BACKSLASH
- set(opentrack-doc-pfx "./doc")
- set(opentrack-doc-src-pfx "./source-code")
- set(opentrack-hier-str RUNTIME DESTINATION ./modules/ LIBRARY DESTINATION ./modules/)
- set(opentrack-i18n-pfx "./i18n")
- set(opentrack-i18n-path "./i18n")
- set(opentrack-hier-debug "./debug")
-else()
- set(opentrack-hier-pfx "libexec/opentrack")
- set(opentrack-hier-path "/../libexec/opentrack/") # MUST HAVE A TRAILING BACKSLASH
- set(opentrack-hier-doc "/share/doc/opentrack/") # MUST HAVE A TRAILING BACKSLASH
- set(opentrack-doc-pfx "./share/doc/opentrack")
- set(opentrack-doc-src-pfx "./share/doc/opentrack/source-code")
- set(opentrack-install-rpath "${CMAKE_INSTALL_PREFIX}/${opentrack-hier-pfx}")
- set(opentrack-hier-str ARCHIVE DESTINATION lib/opentrack LIBRARY DESTINATION ${opentrack-hier-pfx} RUNTIME DESTINATION bin)
- set(opentrack-i18n-pfx "./libexec/opentrack/i18n")
- set(opentrack-i18n-path "../libexec/opentrack/i18n")
-endif()
-
-function(otr_escape_string var str)
- string(REGEX REPLACE "([\$\\\"#])" "\\\\\\1" tmp__ "${str}")
- set(${var} "${tmp__}" PARENT_SCOPE)
-endfunction()
-
-function(otr_setup_refresh_install_dir)
- if((NOT CMAKE_INSTALL_PREFIX STREQUAL "") AND (NOT opentrack-doc-src-pfx STREQUAL ""))
- otr_escape_string(dir "${CMAKE_INSTALL_PREFIX}/${opentrack-doc-src-pfx}/")
- install(CODE "file(REMOVE_RECURSE \"${dir}\")")
- endif()
-endfunction()
-
-set(opentrack-contrib-pfx "${opentrack-doc-pfx}/contrib")
-
-set(opentrack-binary-suffix "")
-if(APPLE)
- set(opentrack-binary-suffix ".bin")
-elseif(WIN32)
- set(opentrack-binary-suffix ".exe")
-endif()
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+# PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+include_guard(GLOBAL)
+
+if(APPLE)
+ set(opentrack-libexec "Plugins")
+ set(opentrack-runtime-libexec "/Plugins/") # MUST HAVE A TRAILING BACKSLASH, Used in APP
+ set(opentrack-runtime-doc "/") # MUST HAVE A TRAILING BACKSLASH
+ set(opentrack-bin "${CMAKE_INSTALL_PREFIX}")
+ set(opentrack-doc "doc")
+ set(opentrack-i18n "opentrack.app/Contents/Resources") # used during install
+ set(opentrack-runtime-i18n "../Resources/i18n") # used in application
+ set(opentrack-install-rpath "${CMAKE_INSTALL_PREFIX}/Library")
+elseif(WIN32)
+ set(opentrack-libexec "modules")
+ set(opentrack-runtime-libexec "/${opentrack-libexec}/") # MUST HAVE A TRAILING BACKSLASH
+ set(opentrack-runtime-doc "/doc/") # MUST HAVE A TRAILING BACKSLASH
+ set(opentrack-bin ".")
+ set(opentrack-doc "doc")
+ set(opentrack-i18n "i18n")
+ set(opentrack-runtime-i18n "./i18n")
+ set(opentrack-debug "./debug")
+ set(opentrack-install-rpath "")
+else()
+ set(opentrack-libexec "libexec/opentrack")
+ set(opentrack-runtime-libexec "/../${opentrack-libexec}/") # MUST HAVE A TRAILING BACKSLASH
+ set(opentrack-runtime-doc "/../share/doc/opentrack/") # MUST HAVE A TRAILING BACKSLASH
+ set(opentrack-bin "bin")
+ set(opentrack-doc "share/doc/opentrack")
+ set(opentrack-install-rpath "${CMAKE_INSTALL_PREFIX}/${opentrack-libexec}")
+ set(opentrack-i18n "share/opentrack/i18n")
+ set(opentrack-runtime-i18n "../share/opentrack/i18n")
+endif()
+
+function(otr_escape_string var str)
+ string(REGEX REPLACE "([^_A-Za-z0-9./:-])" "\\\\\\1" str "${str}")
+ set(${var} "${str}" PARENT_SCOPE)
+endfunction()
+
+set(opentrack-contrib-pfx "${opentrack-doc}/contrib")
+
+set(opentrack-binary-suffix "")
+if(WIN32)
+ set(opentrack-binary-suffix ".exe")
+endif()
+
+set(CMAKE_INSTALL_RPATH "${opentrack-install-rpath}")
diff --git a/cmake/opentrack-i18n.cmake b/cmake/opentrack-i18n.cmake
index bc468e8a..1f0c67d9 100644
--- a/cmake/opentrack-i18n.cmake
+++ b/cmake/opentrack-i18n.cmake
@@ -1,63 +1,97 @@
+include_guard(GLOBAL)
+
+add_custom_target(i18n-lupdate)
+add_custom_target(i18n-lrelease DEPENDS i18n-lupdate)
+add_custom_target(i18n ALL DEPENDS i18n-lrelease)
+
function(otr_i18n_for_target_directory n)
set(k "opentrack-${n}")
get_property(lupdate-binary TARGET "${Qt5_LUPDATE_EXECUTABLE}" PROPERTY IMPORTED_LOCATION)
+ #make_directory("${CMAKE_CURRENT_BINARY_DIR}/lang")
+
+ set(ts-files "")
+ foreach(k ${opentrack_all-translations})
+ list(APPEND ts-files "${CMAKE_CURRENT_SOURCE_DIR}/lang/${k}.ts")
+ set_property(GLOBAL APPEND PROPERTY "opentrack-ts-files-${k}" "${CMAKE_CURRENT_SOURCE_DIR}/lang/${k}.ts")
+ endforeach()
+ set(stamp "${CMAKE_CURRENT_BINARY_DIR}/lupdate.stamp")
+
foreach(i ${opentrack_all-translations})
set(t "${CMAKE_CURRENT_SOURCE_DIR}/lang/${i}.ts")
- set(t2 "${CMAKE_CURRENT_BINARY_DIR}/lang/${i}.ts")
- set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY CLEAN_NO_CUSTOM 1)
- set(input ${${k}-cc} ${${k}-hh} ${${k}-ui} ${${k}-rc})
- add_custom_command(OUTPUT "${t2}"
- COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_SOURCE_DIR}/lang"
- COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/lang"
- COMMAND "${lupdate-binary}"
- -I "${CMAKE_SOURCE_DIR}"
- -silent
- -recursive
- -no-obsolete
- -locations none
- .
- -ts "${t}"
- COMMAND "${CMAKE_COMMAND}" -E copy "${t}" "${t2}"
- WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
- DEPENDS ${input}
- COMMENT "Running lupdate for ${n}/${i}")
- set_property(SOURCE ${input} PROPERTY GENERATED TRUE)
- set(target-name "i18n-lang-${i}-module-${n}")
- add_custom_target(${target-name} DEPENDS "${t2}")
- set_property(GLOBAL APPEND PROPERTY "opentrack-ts-files-${i}" "${t2}")
+ set(input "${${k}-all}")
+ if (NOT EXISTS "${t}")
+ file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lang")
+ file(READ "${CMAKE_SOURCE_DIR}/cmake/translation-stub.ts" stub)
+ string(REPLACE "@LANG@" "${i}" stub "${stub}")
+ file(WRITE "${t}" "${stub}")
+ endif()
endforeach()
-endfunction()
-
-function(otr_merge_translations)
- install(CODE "file(REMOVE_RECURSE \"\${CMAKE_INSTALL_PREFIX}/i18n\")")
- get_property(variant GLOBAL PROPERTY opentrack-variant)
- if(NOT ".${variant}" STREQUAL ".default")
- set(force-skip-update TRUE)
+ # all sorts of problems
+ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
+ set(to-null "2>NUL")
else()
- set(force-skip-update FALSE)
+ set(to-null "2>/dev/null")
endif()
- set(all-qm-files "")
+ add_custom_command(OUTPUT "${stamp}"
+ COMMAND "${lupdate-binary}"
+ -I "${CMAKE_SOURCE_DIR}"
+ -silent
+ -recursive
+ -no-obsolete
+ -locations none
+ .
+ -ts ${ts-files}
+ ${to-null}
+ COMMAND "${CMAKE_COMMAND}" -E touch "${stamp}"
+ #BYPRODUCTS ${ts-files}
+ DEPENDS "opentrack-${n}"
+ COMMENT "Running lupdate for ${n}"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
+ set(target-name "i18n-module-${n}")
+ add_custom_target(${target-name} DEPENDS "${stamp}" COMMENT "")
+ add_dependencies(i18n-lupdate ${target-name})
+endfunction()
+
+function(otr_merge_translations)
+ otr_escape_string(i18n-pfx "${opentrack-i18n}")
+ #install(CODE "message(FATAL_ERROR \"foo \${CMAKE_INSTALL_PREFIX}\")")
+ #install(CODE "file(REMOVE_RECURSE \"\${CMAKE_INSTALL_PREFIX}/${i18n-pfx}\")")
foreach(i ${opentrack_all-translations})
get_property(ts-files GLOBAL PROPERTY "opentrack-ts-files-${i}")
get_property(lrelease-binary TARGET "${Qt5_LRELEASE_EXECUTABLE}" PROPERTY IMPORTED_LOCATION)
- set(qm-output "${CMAKE_CURRENT_BINARY_DIR}/${i}.qm")
- list(APPEND all-qm-files "${qm-output}")
+ set(qm-output "${CMAKE_BINARY_DIR}/${i}.qm")
+
+ # whines about duplicate messages since tracker-pt-base is static
+ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
+ set(to-null "2>NUL")
+ else()
+ set(to-null "2>/dev/null")
+ endif()
add_custom_command(OUTPUT "${qm-output}"
- COMMAND "${lrelease-binary}" -nounfinished -silent ${ts-files} -qm "${qm-output}"
- DEPENDS ${ts-files}
- COMMENT "Running lrelease for ${i}")
+ COMMAND "${lrelease-binary}"
+ -nounfinished
+ -silent
+ #-verbose
+ ${ts-files}
+ -qm "${qm-output}"
+ ${to-null}
+ DEPENDS i18n-lupdate ${ts-files}
+ COMMENT "Running lrelease for ${i}"
+ WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
+
+ set(target-name i18n-qm-${i})
+ add_custom_target(${target-name} DEPENDS "${qm-output}")
+ add_dependencies(i18n-lrelease ${target-name})
install(FILES "${qm-output}"
- DESTINATION "${CMAKE_INSTALL_PREFIX}/${opentrack-i18n-pfx}"
+ DESTINATION "${opentrack-i18n}"
PERMISSIONS ${opentrack-perms-file})
endforeach()
-
- add_custom_target(i18n ALL DEPENDS ${all-qm-files})
endfunction()
diff --git a/cmake/opentrack-install.cmake b/cmake/opentrack-install.cmake
index f50b93d9..5aac983b 100644
--- a/cmake/opentrack-install.cmake
+++ b/cmake/opentrack-install.cmake
@@ -1,58 +1,58 @@
-macro(otr_inst2 path)
+include_guard(GLOBAL)
+macro(otr_install_misc path)
install(${ARGN} DESTINATION "${path}" PERMISSIONS ${opentrack-perms-file})
endmacro()
-macro(otr_inst_exec path)
+macro(otr_install_exec path)
install(${ARGN} DESTINATION "${path}" PERMISSIONS ${opentrack-perms-file})
endmacro()
-macro(otr_inst_dir path)
+macro(otr_install_dir path)
install(
DIRECTORY ${ARGN} DESTINATION "${path}"
FILE_PERMISSIONS ${opentrack-perms-file}
DIRECTORY_PERMISSIONS ${opentrack-perms-dir}
+ PATTERN ".gtm" EXCLUDE
)
endmacro()
-function(install_sources)
- if(FALSE)
- otr_setup_refresh_install_dir()
- get_property(source-dirs GLOBAL PROPERTY opentrack-all-source-dirs)
- foreach(k ${source-dirs})
- file(RELATIVE_PATH dest "${CMAKE_SOURCE_DIR}" "${k}")
- otr_inst_dir("${opentrack-doc-src-pfx}" "${dest}")
- endforeach()
- otr_inst_dir("${opentrack-doc-src-pfx}" "${CMAKE_SOURCE_DIR}/cmake")
- otr_inst_dir("${opentrack-doc-src-pfx}" "${CMAKE_SOURCE_DIR}/bin")
- otr_inst2("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/CMakeLists.txt")
- otr_inst2("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/README.md")
- otr_inst2("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/CONTRIBUTING.md")
- otr_inst2("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/WARRANTY.txt")
- otr_inst2("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/OPENTRACK-LICENSING.txt")
- otr_inst2("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/AUTHORS.md")
- endif()
+function(cleanup_visual_studio_debug)
+ otr_escape_string(pfx "${CMAKE_INSTALL_PREFIX}")
+ install(CODE "file(REMOVE_RECURSE \"${pfx}/.vs\")")
endfunction()
-otr_inst_dir("${opentrack-doc-pfx}" ${CMAKE_SOURCE_DIR}/3rdparty-notices)
-otr_inst_dir("${opentrack-doc-pfx}" "${CMAKE_SOURCE_DIR}/settings" "${CMAKE_SOURCE_DIR}/contrib")
+otr_install_dir("${opentrack-doc}" ${CMAKE_SOURCE_DIR}/3rdparty-notices)
+otr_install_dir("${opentrack-doc}" "${CMAKE_SOURCE_DIR}/settings" "${CMAKE_SOURCE_DIR}/contrib")
+otr_install_dir("${opentrack-libexec}" "${CMAKE_SOURCE_DIR}/presets")
if(WIN32)
- otr_inst2(. FILES "${CMAKE_SOURCE_DIR}/bin/qt.conf")
- otr_inst2(. FILES "${CMAKE_SOURCE_DIR}/bin/cleye.config")
- otr_inst2(${opentrack-hier-pfx} FILES "${CMAKE_SOURCE_DIR}/bin/cleye.config")
+ otr_install_misc(. FILES "${CMAKE_SOURCE_DIR}/bin/qt.conf")
+ otr_install_misc(. FILES "${CMAKE_SOURCE_DIR}/bin/cleye.config")
+ otr_install_misc(${opentrack-libexec} FILES "${CMAKE_SOURCE_DIR}/bin/cleye.config")
endif()
-otr_inst2("${opentrack-doc-pfx}" FILES ${CMAKE_SOURCE_DIR}/README.md)
+otr_install_misc("${opentrack-doc}" FILES ${CMAKE_SOURCE_DIR}/README.md)
-otr_inst_exec("${opentrack-hier-pfx}" FILES "${CMAKE_SOURCE_DIR}/bin/freetrackclient.dll")
-otr_inst_exec("${opentrack-hier-pfx}" FILES
+otr_install_misc("${opentrack-doc}" FILES "${CMAKE_SOURCE_DIR}/README.md")
+otr_install_misc("${opentrack-doc}" FILES "${CMAKE_SOURCE_DIR}/.github/CONTRIBUTING.md")
+otr_install_misc("${opentrack-doc}" FILES "${CMAKE_SOURCE_DIR}/WARRANTY.txt")
+otr_install_misc("${opentrack-doc}" FILES "${CMAKE_SOURCE_DIR}/OPENTRACK-LICENSING.txt")
+otr_install_misc("${opentrack-doc}" FILES "${CMAKE_SOURCE_DIR}/AUTHORS.md")
+
+# this must be done last because the files may be in use already
+# do it last so in case of file-in-use failure, the rest is installed
+
+if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
+ cleanup_visual_studio_debug()
+endif()
+
+# For now copy third party needed files into a seperate direcvtory instead of the plugins directory
+if (APPLE)
+ set(OSX_POST_INSTALL_DIR "/../thirdparty")
+endif()
+otr_install_exec("${opentrack-libexec}${OSX_POST_INSTALL_DIR}" FILES "${CMAKE_SOURCE_DIR}/bin/freetrackclient.dll")
+otr_install_exec("${opentrack-libexec}${OSX_POST_INSTALL_DIR}" FILES "${CMAKE_SOURCE_DIR}/bin/freetrackclient64.dll")
+otr_install_exec("${opentrack-libexec}${OSX_POST_INSTALL_DIR}" FILES
"${CMAKE_SOURCE_DIR}/bin/NPClient.dll"
"${CMAKE_SOURCE_DIR}/bin/NPClient64.dll"
"${CMAKE_SOURCE_DIR}/bin/TrackIR.exe")
-
-otr_inst2("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/CMakeLists.txt")
-otr_inst2("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/README.md")
-otr_inst2("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/CONTRIBUTING.md")
-otr_inst2("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/WARRANTY.txt")
-otr_inst2("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/OPENTRACK-LICENSING.txt")
-otr_inst2("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/AUTHORS.md")
diff --git a/cmake/opentrack-load-user-settings.cmake b/cmake/opentrack-load-user-settings.cmake
index ede46d7c..fdb63936 100644
--- a/cmake/opentrack-load-user-settings.cmake
+++ b/cmake/opentrack-load-user-settings.cmake
@@ -1,4 +1,5 @@
-if(WIN32)
+include_guard(GLOBAL)
+if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
set(__sdk_username "$ENV{USERNAME}")
else()
set(__sdk_username "$ENV{USER}")
@@ -9,16 +10,27 @@ if(".${__sdk_username}" STREQUAL ".")
endif()
if(WIN32)
- set(__sdk_os "windows")
+ set(__sdk_target_os "windows")
else()
- set(__sdk_os "$ENV{OSTYPE}")
+ string(REGEX MATCH "^Linux (.+)-Microsoft\$" __sdk_target_os_match "${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION}")
+ if(__sdk_target_os_match)
+ set(__sdk_target_os "Windows-WSL")
+ else()
+ set(__sdk_target_os "${CMAKE_SYSTEM_NAME}")
+ endif()
endif()
-include(CMakeDetermineCCompiler)
-include(CMakeDetermineCXXCompiler)
+if(NOT CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME)
+ set(__sdk_host_os "${CMAKE_HOST_SYSTEM_NAME}-cross-")
+else()
+ set(__sdk_host_os "")
+endif()
-set(__sdk_paths_filename "${CMAKE_SOURCE_DIR}/sdk-paths-${__sdk_username}@${CMAKE_CXX_COMPILER_ID}-${__sdk_os}.cmake")
+set(__sdk_paths_filename "${CMAKE_CURRENT_SOURCE_DIR}/sdk-paths-${__sdk_username}@${CMAKE_CXX_COMPILER_ID}-${__sdk_host_os}${__sdk_target_os}.cmake")
if(EXISTS "${__sdk_paths_filename}")
+ message(STATUS "Loading user settings '${__sdk_paths_filename}'")
include("${__sdk_paths_filename}")
+else()
+ message(STATUS "User settings file '${__sdk_paths_filename}' doesn't exist")
endif()
diff --git a/cmake/opentrack-mrproper.cmake b/cmake/opentrack-mrproper.cmake
index faa11bd4..6efb0be3 100644
--- a/cmake/opentrack-mrproper.cmake
+++ b/cmake/opentrack-mrproper.cmake
@@ -7,6 +7,8 @@
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
+include_guard(GLOBAL)
+
function(cleanup_build_dir)
file(GLOB_RECURSE files LIST_DIRECTORIES TRUE RELATIVE "${CMAKE_BINARY_DIR}" "*")
diff --git a/cmake/opentrack-opencv.cmake b/cmake/opentrack-opencv.cmake
new file mode 100644
index 00000000..3acadf4a
--- /dev/null
+++ b/cmake/opentrack-opencv.cmake
@@ -0,0 +1,4 @@
+include_guard(GLOBAL)
+
+include(opentrack-boilerplate)
+find_package(OpenCV QUIET)
diff --git a/cmake/opentrack-org.cmake b/cmake/opentrack-org.cmake
new file mode 100644
index 00000000..107c829b
--- /dev/null
+++ b/cmake/opentrack-org.cmake
@@ -0,0 +1,21 @@
+include_guard(GLOBAL)
+function(otr_write_org)
+ get_property(ident GLOBAL PROPERTY opentrack-ident)
+ if (ident STREQUAL "")
+ message(FATAL_ERROR "must set global property `opentrack-ident' in `opentrack-variant.cmake'")
+ endif()
+ otr_escape_string(ident "${ident}")
+ set(new-str "#pragma once
+#define OPENTRACK_ORG \"${ident}\"
+")
+
+ set(filename "${CMAKE_BINARY_DIR}/opentrack-org.hxx")
+ set(old-str "")
+ if(EXISTS "${filename}")
+ file(READ "${filename}" old-str)
+ endif()
+ if(NOT old-str STREQUAL new-str)
+ file(WRITE "${filename}" "${new-str}")
+ endif()
+endfunction()
+otr_write_org()
diff --git a/cmake/opentrack-pkg-config.cmake b/cmake/opentrack-pkg-config.cmake
new file mode 100644
index 00000000..0f13defc
--- /dev/null
+++ b/cmake/opentrack-pkg-config.cmake
@@ -0,0 +1,31 @@
+include_guard(GLOBAL)
+include(FindPkgConfig)
+
+function(otr_pkgconfig_ ret target)
+ set(${ret} 1 PARENT_SCOPE)
+ foreach(i ${ARGN})
+ set(k pkg-config_${i})
+ pkg_check_modules(${k} QUIET ${i})
+ if(${${k}_FOUND})
+ target_compile_options(${target} PRIVATE "${${k}_CFLAGS}")
+ target_link_options(${target} PRIVATE ${${k}_LDFLAGS})
+ target_include_directories(${target} SYSTEM PRIVATE ${${k}_INCLUDE_DIRS} ${${k}_INCLUDEDIR})
+ target_link_libraries(${target} ${${k}_LIBRARIES})
+ else()
+ set(${ret} 0 PARENT_SCOPE)
+ endif()
+ endforeach()
+endfunction()
+
+function(otr_pkgconfig target)
+ set(ret "")
+ otr_pkgconfig_(ret "${target}" ${ARGN})
+ foreach(i ${ARGN})
+ set(k pkg-config_${i})
+ if(NOT ${${k}_FOUND})
+ message(FATAL_ERROR "Can't find '${i}'. Please install development files for this package.")
+ endif()
+ endforeach()
+endfunction()
+
+
diff --git a/cmake/opentrack-platform.cmake b/cmake/opentrack-platform.cmake
index eced4201..638260f1 100644
--- a/cmake/opentrack-platform.cmake
+++ b/cmake/opentrack-platform.cmake
@@ -21,133 +21,153 @@
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
-if(NOT CMAKE_BUILD_TYPE)
- set(CMAKE_BUILD_TYPE "RELEASE" CACHE STRING "" FORCE)
- set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install")
-endif()
+include_guard(GLOBAL)
-if(APPLE)
- if(NOT CMAKE_OSX_ARCHITECTURES)
- set(CMAKE_OSX_ARCHITECTURES "x86_64")
- endif()
+if(MSVC AND MSVC_VERSION LESS "1915" AND NOT ".${CMAKE_CXX_COMPILER_ID}" STREQUAL ".Clang")
+ message(FATAL_ERROR "Visual Studio too old. Use Visual Studio 2017 or newer.")
endif()
-if(MSVC AND MSVC_VERSION LESS "1900" AND NOT ".${CMAKE_CXX_COMPILER_ID}" STREQUAL ".Clang")
- message(FATAL_ERROR "Visual Studio too old. Use Visual Studio 2015 Update 3 or newer.")
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # for clang
+
+string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE)
+set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "" FORCE)
+
+include_directories("${CMAKE_SOURCE_DIR}")
+
+set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
+
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_DEFAULT 20)
+set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
+set(CMAKE_CXX_EXTENSIONS FALSE)
+
+set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
+set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
+set(CMAKE_SKIP_INSTALL_RPATH FALSE)
+set(CMAKE_SKIP_RPATH FALSE)
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+set(CMAKE_AUTOMOC OFF)
+set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
+
+set(CMAKE_C_VISIBILITY_PRESET hidden)
+set(CMAKE_CXX_VISIBILITY_PRESET hidden)
+
+
+if(NOT WIN32 AND NOT APPLE)
+ include(opentrack-pkg-config)
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_COMPILER_IS_GNUCXX TRUE)
- set(CMAKE_COMPILER_IS_CLANG TRUE)
+ set(CMAKE_COMPILER_IS_CLANGXX TRUE)
endif()
if(CMAKE_C_COMPILER_ID STREQUAL "Clang")
set(CMAKE_COMPILER_IS_GNUCC TRUE)
- set(CMAKE_COMPILER_IS_GNUC TRUE)
set(CMAKE_COMPILER_IS_CLANG TRUE)
endif()
-if((NOT CMAKE_COMPILER_IS_GNUCXX) EQUAL (NOT (NOT CMAKE_COMPILER_IS_GNUCC)))
- message(FATAL_ERROR "use either use both gcc and g++ or neither")
+if(APPLE AND NOT CMAKE_OSX_ARCHITECTURES)
+ set(CMAKE_OSX_ARCHITECTURES "x86_64")
+ set(opentrack-intel TRUE)
+elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "amd64.*|x86_64.*|AMD64.*|i[0-9]86.*|x86.*")
+ set(opentrack-intel TRUE)
+elseif(MSVC AND CMAKE_SYSTEM_NAME STREQUAL "Windows" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "")
+ set(opentrack-intel TRUE)
+else()
+ set(opentrack-intel FALSE)
+endif()
+
+if(CMAKE_SIZEOF_VOID_P GREATER_EQUAL 8)
+ set(opentrack-64bit TRUE)
+else()
+ set(opentrack-64bit FALSE)
endif()
IF(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LINUX TRUE)
endif()
-if(MSVC)
- add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS -D_NO_DEBUG_HEAP)
- add_definitions(-D_ITERATOR_DEBUG_LEVEL=0)
- add_definitions(-D_HAS_EXCEPTIONS=0)
- add_definitions(-D_SILENCE_CXX17_NEGATORS_DEPRECATION_WARNING -D_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Zi")
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Zi")
-
- if(NOT CMAKE_COMPILER_IS_CLANG)
- if(MSVC_VERSION GREATER 1909) # visual studio 2017
- set(__stuff "-permissive-")
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${__stuff}")
- set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} ${__stuff}")
- endif()
+if(CMAKE_COMPILER_IS_GNUCXX AND NOT APPLE)
+ add_compile_options(-fuse-cxa-atexit)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std:c++latest")
- else()
- set(__stuff "-fms-compatibility -fms-compatibility-version=1911 -fms-extensions")
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${__stuff} -std:c++latest -Xclang -std=c++17")
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${__stuff}")
+ if(LINUX) # assume binutils
+ add_link_options(-Wl,--exclude-libs,ALL)
+ add_link_options(-Wl,-z,relro,-z,now)
+ add_link_options(-Wl,--as-needed)
+ add_link_options(-Wl,-z,noexecstack)
+ add_compile_options(-fno-plt)
endif()
-
- if(opentrack-64bit)
- set(ent "-HIGHENTROPYVA")
- else()
- set(ent "")
- endif()
-
- foreach (i SHARED MODULE EXE)
- set(CMAKE_${i}_LINKER_FLAGS "${CMAKE_${i}_LINKER_FLAGS} -DYNAMICBASE -NXCOMPAT -DEBUG ${ent}")
- endforeach()
endif()
if(WIN32)
- if(CMAKE_COMPILER_IS_GNUCXX AND NOT MSVC)
- set(CMAKE_RC_COMPILER_INIT i686-w64-mingw32-windres)
- set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> --use-temp-file -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
- endif()
- enable_language(RC)
- add_definitions(-D_USE_MATH_DEFINES=1)
-endif(WIN32)
-
-if(opentrack-install-rpath)
- set(CMAKE_INSTALL_RPATH "${opentrack-install-rpath}")
-else()
- set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}")
+ add_definitions(-D_USE_MATH_DEFINES=1 -DSTRSAFE_NO_DEPRECATE)
endif()
-set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
-set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
-set(CMAKE_SKIP_INSTALL_RPATH FALSE)
-set(CMAKE_SKIP_RPATH FALSE)
-set(CMAKE_INCLUDE_CURRENT_DIR ON)
-set(CMAKE_AUTOMOC OFF)
-set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+if(MINGW)
+ add_definitions(-DMINGW_HAS_SECURE_API)
+endif()
-include_directories("${CMAKE_SOURCE_DIR}")
+if(MSVC)
+ set(CMAKE_RC_FLAGS "/nologo /DWIN32")
-if(APPLE)
- set(CMAKE_MACOSX_RPATH OFF)
- set(apple-frameworks "-stdlib=libc++ -framework Cocoa -framework CoreFoundation -lobjc -lz -framework Carbon")
- set(CMAKE_SHARED_LINKER_FLAGS " ${apple-frameworks} ${CMAKE_SHARED_LINKER_FLAGS}")
- #set(CMAKE_STATIC_LINKER_FLAGS " ${apple-frameworks} ${CMAKE_STATIC_LINKER_FLAGS}")
- set(CMAKE_EXE_LINKER_FLAGS " ${apple-frameworks} ${CMAKE_EXE_LINKER_FLAGS}")
- set(CMAKE_MODULE_LINKER_FLAGS " ${apple-frameworks} ${CMAKE_MODULE_LINKER_FLAGS}")
- set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
-endif()
+ add_definitions(-DNOMINMAX)
+ add_definitions(-DWIN32_LEAN_AND_MEAN)
+ add_definitions(-D_CRT_SECURE_NO_WARNINGS)
+ add_definitions(-D_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1)
+ add_definitions(-D_SCL_SECURE_NO_WARNINGS)
-if(NOT MSVC)
- set(CMAKE_CXX_STANDARD 17)
- set(CMAKE_CXX_STANDARD_DEFAULT 17)
- set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
- set(CMAKE_CXX_EXTENSIONS FALSE)
-endif()
+ #add_compile_options(-EHsc)
+ add_definitions(-D_HAS_EXCEPTIONS=0)
-foreach(k _RELEASE _DEBUG _RELWITHDEBINFO _MINSIZEREL)
- set(CMAKE_C_FLAGS${k} "${CMAKE_C_FLAGS${k}} -UNDEBUG")
- set(CMAKE_CXX_FLAGS${k} "${CMAKE_CXX_FLAGS${k}} -UNDEBUG")
-endforeach()
+ add_definitions(-D_ENABLE_EXTENDED_ALIGNED_STORAGE)
+ add_definitions(-D_ENABLE_ATOMIC_ALIGNMENT_FIX)
+ add_definitions(-D_SILENCE_CXX17_NEGATORS_DEPRECATION_WARNING)
+ add_definitions(-D_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING)
+ add_definitions(-D_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING)
-set_property(GLOBAL PROPERTY USE_FOLDERS OFF)
+ add_compile_options(-permissive-)
-# nix -rdynamic passed from Linux-GNU.cmake
-if(CMAKE_COMPILER_IS_GNUCXX)
- set(__LINUX_COMPILER_GNU 1)
- set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS)
- set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS)
-endif()
+ if(opentrack-64bit)
+ add_link_options(-HIGHENTROPYVA)
+ endif()
-if(MINGW)
- add_definitions(-DMINGW_HAS_SECURE_API)
+ add_link_options(-DYNAMICBASE -NXCOMPAT)
+ add_link_options(-WX)
+ add_link_options(-ignore:4020)
+ add_link_options(-ignore:4217) # debug build
+
+
+ if(MSVC_VERSION GREATER_EQUAL 1913)
+ if(NOT MSVC_VERSION GREATER_EQUAL 1929)
+ add_compile_options(-experimental:external)
+ endif()
+ add_compile_options(-external:W0 -external:anglebrackets)
+ endif()
+ add_compile_options(-Zc:preprocessor)
+ #add_compile_options(-Zc:inline)
+
+ #C4457: declaration of 'id' hides function parameter
+ #C4456: declaration of 'i' hides previous local declaration
+ #C4263 - member function does not override any base class virtual member function
+ #C4264 - no override available for virtual member function from base class, function is hidden
+ #C4265 - class has virtual functions, but destructor is not virtual
+ #C4266 - no override available for virtual member function from base type, function is hidden
+ #C4928 - illegal copy-initialization, more than one user-defined conversion has been implicitly applied
+ #C4200: nonstandard extension used: zero-sized array in struct/union
+ #C4459: declaration of 'eps' hides global declaration
+
+ set(warns-disable 4530 4577 4789 4244 4702 4530 4244 4127 4458 4456 4251 4100 4702 4457 4200 4459)
+
+ foreach(i ${warns-disable})
+ add_compile_options(-wd${i})
+ endforeach()
endif()
-if(UNIX AND NOT APPLE)
+if(NOT MSVC)
include(FindPkgConfig)
endif()
+
+if(LINUX AND CMAKE_COMPILER_IS_CLANG)
+ link_libraries(atomic)
+endif()
diff --git a/cmake/opentrack-policy.cmake b/cmake/opentrack-policy.cmake
index e4fded7a..5fd8647f 100644
--- a/cmake/opentrack-policy.cmake
+++ b/cmake/opentrack-policy.cmake
@@ -1,5 +1,24 @@
-foreach(k CMP0020 CMP0022 CMP0058 CMP0028 CMP0042 CMP0063 CMP0053 CMP0011 CMP0054 CMP0012)
+include_guard(GLOBAL)
+
+set(_policies
+ CMP0020
+ CMP0022
+ CMP0058
+ CMP0028
+ CMP0042
+ CMP0063
+ CMP0053
+ CMP0011
+ CMP0054
+ CMP0012
+ CMP0069
+ CMP0063
+ CMP0074
+)
+foreach(k ${_policies})
if(POLICY ${k})
cmake_policy(SET ${k} NEW)
endif()
endforeach()
+
+set(CMAKE_INSTALL_MESSAGE LAZY)
diff --git a/cmake/opentrack-qt.cmake b/cmake/opentrack-qt.cmake
index 911eec5b..1735e836 100644
--- a/cmake/opentrack-qt.cmake
+++ b/cmake/opentrack-qt.cmake
@@ -1,57 +1,45 @@
-find_package(Qt5 REQUIRED COMPONENTS Core Network Widgets LinguistTools Gui QUIET)
+include_guard(GLOBAL)
if(WIN32)
find_package(Qt5Gui REQUIRED COMPONENTS QWindowsIntegrationPlugin)
endif()
-find_package(Qt5 COMPONENTS SerialPort QUIET)
+set(qt-required-components Core Network Widgets LinguistTools Gui)
+set(qt-optional-components SerialPort)
+set(qt-imported-targets Qt5::Core Qt5::Gui Qt5::Network Qt5::Widgets)
+if(APPLE)
+ list(APPEND qt-required-components "DBus")
+ list(APPEND qt-optional-components "Multimedia")
+ list(APPEND qt-imported-targets Qt5::DBus Qt5::Multimedia)
+endif()
-include_directories(SYSTEM ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS})
-add_definitions(${Qt5Core_DEFINITIONS} ${Qt5Gui_DEFINITIONS} ${Qt5Widgets_DEFINITIONS} ${Qt5Network_DEFINITIONS})
-set(MY_QT_LIBS ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES} ${Qt5Widgets_LIBRARIES} ${Qt5Network_LIBRARIES})
+find_package(Qt5 REQUIRED COMPONENTS ${qt-required-components} QUIET)
+find_package(Qt5 COMPONENTS ${qt-optional-components} QUIET)
-function(otr_pdb_for_dll varname path)
- set("${varname}" "" PARENT_SCOPE)
- get_filename_component(dir "${path}" DIRECTORY)
- get_filename_component(name-only "${path}" NAME_WE)
- set(pdb-path "${dir}/${name-only}.pdb")
- if(EXISTS "${pdb-path}")
- set("${varname}" "${pdb-path}" PARENT_SCOPE)
- endif()
-endfunction()
+set(MY_QT_LIBS ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES} ${Qt5Widgets_LIBRARIES} ${Qt5Network_LIBRARIES})
+if(APPLE)
+ list(APPEND MY_QT_LIBS ${Qt5Multimedia_LIBRARIES} ${Qt5DBus_LIBRARIES})
+endif()
function(otr_install_qt_libs)
- if(WIN32)
- foreach(i Qt5::Core Qt5::Gui Qt5::Network Qt5::SerialPort Qt5::Widgets)
- get_property(path TARGET "${i}" PROPERTY LOCATION)
- if("${path}" STREQUAL "")
- message(FATAL_ERROR "${i} ${path}")
- endif()
- install(FILES "${path}" DESTINATION .)
- if(MSVC AND opentrack_install-debug-info)
- otr_pdb_for_dll(pdb-path "${path}")
- if(pdb-path)
- install(FILES "${pdb-path}" DESTINATION "${opentrack-hier-debug}")
- endif()
- endif()
- endforeach()
-
- get_property(path TARGET Qt5::QWindowsIntegrationPlugin PROPERTY LOCATION)
- install(FILES "${path}" DESTINATION ./platforms)
- if(MSVC AND opentrack_install-debug-info)
- otr_pdb_for_dll(pdb-path "${path}")
- if(pdb-path)
- install(FILES "${pdb-path}" DESTINATION "${opentrack-hier-debug}")
- endif()
+ foreach(i ${qt-imported-targets})
+ if(NOT TARGET "${i}")
+ continue()
endif()
+ otr_install_lib(${i} ".")
+ endforeach()
+ if(WIN32)
+ otr_install_lib(Qt5::QWindowsIntegrationPlugin "./platforms")
endif()
endfunction()
-otr_install_qt_libs()
+if(WIN32 OR APPLE)
+ otr_install_qt_libs()
+endif()
function(otr_qt n)
if(".${${n}-cc}${${n}-cxx}${${n}-hh}" STREQUAL ".")
message(FATAL_ERROR "project ${n} not globbed")
endif()
- qt5_wrap_cpp(${n}-moc ${${n}-hh} OPTIONS --no-notes)
+ qt5_wrap_cpp(${n}-moc ${${n}-hh} OPTIONS --no-notes -I "${CMAKE_CURRENT_BINARY_DIR}" -I "${CMAKE_SOURCE_DIR}")
qt5_wrap_ui(${n}-uih ${${n}-ui})
qt5_add_resources(${n}-rcc ${${n}-rc})
@@ -61,3 +49,20 @@ function(otr_qt n)
endforeach()
set(${n}-all "${${n}-all}" PARENT_SCOPE)
endfunction()
+
+function(otr_qt2 n)
+ target_include_directories("${n}" SYSTEM PRIVATE
+ ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS}
+ )
+ target_compile_definitions("${n}" PRIVATE
+ ${Qt5Core_DEFINITIONS} ${Qt5Gui_DEFINITIONS} ${Qt5Widgets_DEFINITIONS} ${Qt5Network_DEFINITIONS}
+ -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
+ -DQT_MESSAGELOGCONTEXT
+ )
+ if(CMAKE_COMPILER_IS_GNUCXX)
+ set_property(SOURCE ${${n}-moc} ${${n}-rcc}
+ APPEND_STRING PROPERTY COMPILE_FLAGS " -w -Wno-error ")
+ endif()
+endfunction()
+
+include_directories("${CMAKE_BINARY_DIR}")
diff --git a/cmake/opentrack-rift.cmake b/cmake/opentrack-rift.cmake
deleted file mode 100644
index 88adbb3e..00000000
--- a/cmake/opentrack-rift.cmake
+++ /dev/null
@@ -1,37 +0,0 @@
-function(otr_rift proj opt)
- if(${opt})
- set(link-flags)
- set(c-flags)
- if(APPLE)
- set(link-flags "-framework CoreFoundation -framework CoreGraphics -framework IOKit -framework Quartz")
- set(c-flags "-fno-strict-aliasing")
- elseif(NOT MSVC)
- set(c-flags "-fno-strict-aliasing")
- endif()
- otr_module(${proj} LINK ${link-flags} COMPILE ${c-flags})
- set(proj "opentrack-${proj}")
- target_include_directories(${proj} SYSTEM PUBLIC ${${opt}}/Include ${${opt}}/Src)
- set(c-flags)
- set(link-flags)
- if(MSVC)
- set(ext lib)
- set(p)
- else()
- set(ext a)
- set(p lib)
- endif()
- if(MSVC)
- set(pfx "")
- else()
- set(pfx "lib")
- endif()
- target_link_libraries(${proj} ${${opt}}/${pfx}LibOVR.${ext})
- if(WIN32)
- target_link_libraries(${proj} winmm setupapi ws2_32 imagehlp wbemuuid)
- set(ext)
- set(p)
- elseif(NOT APPLE)
- target_link_libraries(${proj} udev Xinerama)
- endif()
- endif()
-endfunction()
diff --git a/cmake/opentrack-variant.cmake b/cmake/opentrack-variant.cmake
index bae5a451..5cb4bd57 100644
--- a/cmake/opentrack-variant.cmake
+++ b/cmake/opentrack-variant.cmake
@@ -1,31 +1,32 @@
-# XXX that belongs in each variant's directory!
-
-function(otr_dist_select_variant)
- file(GLOB variants "variant/*")
-
- set(variant-list "")
-
- foreach(k ${variants})
- get_filename_component(name "${k}" NAME)
- if(EXISTS "${k}/_variant.cmake" AND EXISTS "${k}/CMakeLists.txt")
- list(APPEND variant-list "${name}")
- else()
- message(FATAL_ERROR "Stray item in variant dir '${name}'")
- endif()
- endforeach()
-
- set(opentrack_variant "default" CACHE STRING "")
- set(dir "${CMAKE_SOURCE_DIR}/variant/${opentrack_variant}")
-
- if(NOT EXISTS "${dir}/_variant.cmake" OR NOT EXISTS "${dir}/CMakeLists.txt")
- set(opentrack_variant "default" CACHE STRING "" FORCE)
- set(dir "${CMAKE_SOURCE_DIR}/variant/${opentrack_variant}")
- endif()
-
- set_property(CACHE opentrack_variant PROPERTY STRINGS "${variant-list}")
-
- include("${dir}/_variant.cmake")
- otr_init_variant()
-
- add_subdirectory("${dir}")
+include_guard(GLOBAL)
+
+function(otr_init_variant)
+ set_property(GLOBAL PROPERTY opentrack-variant "default")
+ set_property(GLOBAL PROPERTY opentrack-ident "opentrack-2.3")
+
+ set(subprojects
+ "tracker-*"
+ "proto-*"
+ "filter-*"
+ "options"
+ "api"
+ "compat"
+ "logic"
+ "dinput"
+ "gui"
+ "main"
+ "x-plane-plugin"
+ "csv"
+ "pose-widget"
+ "spline"
+ "qxt-mini"
+ "macosx"
+ "cv"
+ "migration"
+ "main-window"
+ "video"
+ "video-*"
+ "opentrack"
+ )
+ set_property(GLOBAL PROPERTY opentrack-subprojects "${subprojects}")
endfunction()
diff --git a/cmake/opentrack-version.cmake b/cmake/opentrack-version.cmake
index 24970b59..0ff516f2 100644
--- a/cmake/opentrack-version.cmake
+++ b/cmake/opentrack-version.cmake
@@ -1,3 +1,4 @@
+include_guard(GLOBAL)
include(GetGitRevisionDescription)
find_package(Git QUIET)
@@ -5,19 +6,9 @@ if(GIT_FOUND)
git_describe(OPENTRACK_COMMIT --tags --always)
endif()
-unset(_build_type)
-if(CMAKE_BUILD_TYPE)
- string(TOUPPER ${CMAKE_BUILD_TYPE} _build_type)
- if (NOT _build_type STREQUAL "DEBUG")
- unset(_build_type)
- else()
- set(_build_type "-${_build_type}")
- endif()
-endif()
-
-file(WRITE ${CMAKE_BINARY_DIR}/opentrack-version.h "#define OPENTRACK_VERSION \"${OPENTRACK_COMMIT}${_build_type}\"")
+file(WRITE ${CMAKE_BINARY_DIR}/opentrack-version.hxx "#define OPENTRACK_VERSION \"${OPENTRACK_COMMIT}\"")
-set(version-string "
+set(version-string "\
#ifdef __cplusplus
extern \"C\"
#else
@@ -25,8 +16,7 @@ extern
#endif
const char* const opentrack_version;
-
-const char* const opentrack_version = \"${OPENTRACK_COMMIT}${_build_type}\";
+const char* const opentrack_version = \"${OPENTRACK_COMMIT}\";
")
set(file "${CMAKE_CURRENT_BINARY_DIR}/version.cpp")
@@ -42,5 +32,5 @@ endif()
add_library(opentrack-version STATIC "${file}")
if(NOT MSVC)
- set_property(TARGET opentrack-version APPEND_STRING PROPERTY COMPILE_FLAGS "-fno-lto ")
+ target_compile_options(opentrack-version PRIVATE -fno-lto)
endif()
diff --git a/cmake/opentrack-word-size.cmake b/cmake/opentrack-word-size.cmake
deleted file mode 100644
index 12c34066..00000000
--- a/cmake/opentrack-word-size.cmake
+++ /dev/null
@@ -1,11 +0,0 @@
-include(CheckTypeSize)
-check_type_size(void* opentrack-word-size BUILTIN_TYPES_ONLY LANGUAGE C)
-if(NOT ((opentrack-word-size EQUAL 4) OR (opentrack-word-size EQUAL 8)))
- message(FATAL_ERROR "word size '${opentrack-word-size}' either misdetected or really not 4 or 8")
-endif()
-set(opentrack-word-size ${opentrack-word-size})
-if(opentrack-word-size GREATER 4)
- set(opentrack-64bit TRUE)
-else()
- set(opentrack-64bit FALSE)
-endif()
diff --git a/cmake/translation-stub.ts b/cmake/translation-stub.ts
new file mode 100644
index 00000000..9d616636
--- /dev/null
+++ b/cmake/translation-stub.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="@LANG@">
+</TS>
diff --git a/compat/CMakeLists.txt b/compat/CMakeLists.txt
index b58b54e9..dc81436d 100644
--- a/compat/CMakeLists.txt
+++ b/compat/CMakeLists.txt
@@ -1,14 +1,9 @@
otr_module(compat NO-COMPAT BIN)
if(NOT WIN32 AND NOT APPLE)
- target_link_libraries(opentrack-compat rt)
+ target_link_libraries(${self} rt)
endif()
if(WIN32)
- target_link_libraries(opentrack-compat winmm strmiids)
-endif()
-
-if(CMAKE_COMPILER_IS_GNUCXX)
- set_property(SOURCE nan.cpp APPEND_STRING PROPERTY
- COMPILE_FLAGS "-fno-lto -fno-fast-math -fno-finite-math-only -O0 ")
+ target_link_libraries(${self} strmiids)
endif()
diff --git a/compat/activation-context.cpp b/compat/activation-context.cpp
new file mode 100644
index 00000000..8d34243d
--- /dev/null
+++ b/compat/activation-context.cpp
@@ -0,0 +1,51 @@
+#ifdef _WIN32
+
+#include "activation-context.hpp"
+#include "compat/library-path.hpp"
+
+#include <QString>
+#include <QFile>
+#include <QDebug>
+
+#include <windows.h>
+
+static_assert(sizeof(std::uintptr_t) == sizeof(ULONG_PTR));
+
+activation_context::activation_context(const QString& module_name, int resid)
+{
+ static const QString prefix = OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH + OPENTRACK_LIBRARY_PREFIX;
+ QString path = prefix + module_name;
+ QByteArray name = QFile::encodeName(path);
+
+ ACTCTXA actx = {};
+ actx.cbSize = sizeof(actx);
+ actx.lpResourceName = MAKEINTRESOURCEA(resid);
+ actx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID;
+ actx.lpSource = name.constData();
+
+ handle = CreateActCtxA(&actx);
+
+ if (handle != INVALID_HANDLE_VALUE)
+ {
+ if (!ActivateActCtx(handle, (ULONG_PTR*)&cookie))
+ {
+ qDebug() << "win32: can't set activation context" << GetLastError();
+ ReleaseActCtx(handle);
+ handle = INVALID_HANDLE_VALUE;
+ }
+ else
+ ok = true;
+ } else {
+ qDebug() << "win32: can't create activation context" << GetLastError();
+ }
+}
+
+activation_context::~activation_context()
+{
+ if (handle != INVALID_HANDLE_VALUE)
+ {
+ DeactivateActCtx(0, cookie);
+ ReleaseActCtx(handle);
+ }
+}
+#endif
diff --git a/compat/activation-context.hpp b/compat/activation-context.hpp
new file mode 100644
index 00000000..a3b0429e
--- /dev/null
+++ b/compat/activation-context.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#ifdef _WIN32
+
+#include "export.hpp"
+
+#include <cstdint>
+#include <QString>
+
+class OTR_COMPAT_EXPORT activation_context
+{
+public:
+ explicit activation_context(const QString& module_name, int resid);
+ ~activation_context();
+
+ explicit operator bool() const { return ok; }
+
+private:
+ std::uintptr_t cookie = 0;
+ void* handle = (void*)-1;
+ bool ok = false;
+};
+
+#else
+# error "tried to use win32-only activation context"
+#endif
diff --git a/compat/arch.hpp b/compat/arch.hpp
new file mode 100644
index 00000000..3120116a
--- /dev/null
+++ b/compat/arch.hpp
@@ -0,0 +1,50 @@
+#pragma once
+
+// fix MSVC arch check macros
+
+// this file is too simple to fall under copyright, and
+// can be copied, modified, and redistributed freely with
+// no conditions. there's no warranty. -sh 20181226
+
+#if defined _MSC_VER
+# ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wreserved-id-macro"
+# pragma clang diagnostic ignored "-Wunused-macros"
+# endif
+
+# if defined _M_AMD64
+# undef __x86_64__
+# define __x86_64__ 1
+# elif defined _M_IX86
+# undef __i386__
+# define __i386__ 1
+# endif
+
+# if defined __AVX__ || defined __x86_64__ || \
+ defined _M_IX86 && _M_IX86_FP >= 2
+# undef __SSE__
+# undef __SSE2__
+# undef __SSE3__
+# define __SSE__ 1
+# define __SSE2__ 1
+# define __SSE3__ 1 // assume SSE3 in the _M_IX86_FP >= 2 case
+# endif
+
+# ifdef __clang__
+# pragma clang diagnostic pop
+# endif
+#endif
+
+#if defined __SSE3__
+# define OTR_ARCH_DENORM_DAZ
+# include <pmmintrin.h>
+#elif defined __SSE2__
+# define OTR_ARCH_DENORM_FTZ
+# include <emmintrin.h>
+#endif
+
+#if defined __SSE2__
+# define OTR_ARCH_FPU_MASK
+# include <xmmintrin.h>
+#endif
diff --git a/compat/base-path.cpp b/compat/base-path.cpp
index 3285bac8..2a26dcff 100644
--- a/compat/base-path.cpp
+++ b/compat/base-path.cpp
@@ -1,10 +1,12 @@
+#undef NDEBUG
+#include <cassert>
+
#include "base-path.hpp"
#include <QCoreApplication>
-OTR_COMPAT_EXPORT
-never_inline
const QString& application_base_path()
{
- static QString const& const_path = QCoreApplication::applicationDirPath();
- return const_path;
+ assert(qApp && "logic error");
+ static QString path = QCoreApplication::applicationDirPath();
+ return path;
}
diff --git a/compat/base-path.hpp b/compat/base-path.hpp
index 8e2884fd..7a9d4115 100644
--- a/compat/base-path.hpp
+++ b/compat/base-path.hpp
@@ -1,12 +1,11 @@
#pragma once
-#include "macros.hpp"
#include "export.hpp"
#include <QString>
OTR_COMPAT_EXPORT
-never_inline
const QString& application_base_path();
#define OPENTRACK_BASE_PATH (application_base_path())
+
diff --git a/compat/camera-names.cpp b/compat/camera-names.cpp
index 22dcdd8f..b9511037 100644
--- a/compat/camera-names.cpp
+++ b/compat/camera-names.cpp
@@ -1,95 +1,130 @@
#include "camera-names.hpp"
+#include <algorithm>
+#include <iterator>
+
#ifdef _WIN32
+# include <cwchar>
# define NO_DSHOW_STRSAFE
# include <dshow.h>
-# include <cwchar>
-#elif defined(__unix) || defined(__linux) || defined(__APPLE__)
+#elif defined(__unix) || defined(__linux__) || defined(__APPLE__)
# include <unistd.h>
#endif
-#ifdef __linux
+#ifdef __APPLE__
+# include <QCameraInfo>
+#endif
+
+#ifdef __linux__
# include <fcntl.h>
# include <sys/ioctl.h>
# include <linux/videodev2.h>
# include <cerrno>
+# include <cstring>
#endif
#include <QDebug>
-OTR_COMPAT_EXPORT int camera_name_to_index(const QString &name)
+int camera_name_to_index(const QString &name)
{
auto list = get_camera_names();
- int ret = list.indexOf(name);
- if (ret < 0)
- ret = 0;
+ auto it = std::find_if(list.cbegin(), list.cend(), [&name](const auto& tuple) {
+ const auto& [str, idx] = tuple;
+ return str == name;
+ });
+ if (it != list.cend())
+ {
+ const auto& [ str, idx ] = *it;
+ return idx;
+ }
+
+ return -1;
+}
+
+#ifdef _WIN32
+# include <QRegularExpression>
+
+static QString prop_to_qstring(IPropertyBag* pPropBag, const wchar_t* name)
+{
+ QString ret{};
+ VARIANT var;
+ VariantInit(&var);
+ HRESULT hr = pPropBag->Read(name, &var, nullptr);
+ if (SUCCEEDED(hr))
+ ret = QString{(const QChar*)var.bstrVal, int(std::wcslen(var.bstrVal))};
+ VariantClear(&var);
return ret;
}
-OTR_COMPAT_EXPORT QList<QString> get_camera_names()
+static QString device_path_from_qstring(const QString& str)
+{
+ // language=RegExp prefix=R"/( suffix=)/"
+ static const QRegularExpression regexp{R"/(#vid_([0-9a-f]{4})&pid_([0-9a-f]{4})&mi_([0-9a-f]{2})#([^#]+))/",
+ QRegularExpression::CaseInsensitiveOption};
+ auto match = regexp.match(str);
+ if (!match.hasMatch())
+ return {};
+ QString id = match.captured(4);
+ id.replace('&', '_');
+ return id;
+}
+
+#endif
+
+std::vector<std::tuple<QString, int>> get_camera_names()
{
- QList<QString> ret;
-#if defined(_WIN32)
+ std::vector<std::tuple<QString, int>> ret;
+#ifdef _WIN32
// Create the System Device Enumerator.
HRESULT hr;
CoInitialize(nullptr);
- ICreateDevEnum *pSysDevEnum = NULL;
- hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);
+ ICreateDevEnum *pSysDevEnum = nullptr;
+ hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);
if (FAILED(hr))
{
qDebug() << "failed CLSID_SystemDeviceEnum" << hr;
return ret;
}
// Obtain a class enumerator for the video compressor category.
- IEnumMoniker *pEnumCat = NULL;
+ IEnumMoniker *pEnumCat = nullptr;
hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumCat, 0);
if (hr == S_OK) {
// Enumerate the monikers.
- IMoniker *pMoniker = NULL;
+ IMoniker *pMoniker = nullptr;
ULONG cFetched;
while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
{
IPropertyBag *pPropBag;
- hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
- if (SUCCEEDED(hr)) {
+ hr = pMoniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void **)&pPropBag);
+ if (SUCCEEDED(hr)) {
// To retrieve the filter's friendly name, do the following:
- VARIANT varName;
- VariantInit(&varName);
- hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (SUCCEEDED(hr))
{
- // Display the name in your UI somehow.
- QString str((QChar*)varName.bstrVal, int(std::wcslen(varName.bstrVal)));
- ret.append(str);
+ QString str = prop_to_qstring(pPropBag, L"FriendlyName");
+ QString path = device_path_from_qstring(prop_to_qstring(pPropBag, L"DevicePath"));
+ if (!path.isNull())
+ str += QStringLiteral(" [%1]").arg(path);
+ ret.push_back({ str, (int)ret.size() });
}
- VariantClear(&varName);
-
- ////// To create an instance of the filter, do the following:
- ////IBaseFilter *pFilter;
- ////hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter,
- //// (void**)&pFilter);
- // Now add the filter to the graph.
- //Remember to release pFilter later.
pPropBag->Release();
}
pMoniker->Release();
}
pEnumCat->Release();
}
+#if 0
else
qDebug() << "failed CLSID_VideoInputDeviceCategory" << hr;
+#endif
pSysDevEnum->Release();
#endif
-#ifdef __linux
+
+#ifdef __linux__
for (int i = 0; i < 16; i++) {
- char buf[128];
- sprintf(buf, "/dev/video%d", i);
- if (access(buf, F_OK) == 0)
- ret.append(buf);
- else
- continue;
+ char buf[32];
+ snprintf(buf, sizeof(buf), "/dev/video%d", i);
if (access(buf, R_OK | W_OK) == 0) {
int fd = open(buf, O_RDONLY);
@@ -102,10 +137,16 @@ OTR_COMPAT_EXPORT QList<QString> get_camera_names()
close(fd);
continue;
}
- ret[ret.size()-1] = reinterpret_cast<const char*>(video_cap.card);
+ ret.push_back({ QString((const char*)video_cap.card), i});
close(fd);
}
}
#endif
+#ifdef __APPLE__
+ QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
+ for (const QCameraInfo &cameraInfo : cameras)
+ ret.push_back({ cameraInfo.description(), ret.size() });
+#endif
+
return ret;
}
diff --git a/compat/camera-names.hpp b/compat/camera-names.hpp
index 97184c8c..3585edfe 100644
--- a/compat/camera-names.hpp
+++ b/compat/camera-names.hpp
@@ -8,11 +8,12 @@
#pragma once
-#include <QList>
+#include <vector>
#include <QString>
+# include <tuple>
#include "export.hpp"
-OTR_COMPAT_EXPORT QList<QString> get_camera_names();
+OTR_COMPAT_EXPORT std::vector<std::tuple<QString, int>> get_camera_names();
OTR_COMPAT_EXPORT int camera_name_to_index(const QString &name);
diff --git a/compat/check-visible.cpp b/compat/check-visible.cpp
index 84ddd6d9..621d941a 100644
--- a/compat/check-visible.cpp
+++ b/compat/check-visible.cpp
@@ -1,71 +1,98 @@
#include "check-visible.hpp"
+#include <QMutex>
+#include <QWidget>
+#include <QDebug>
+
+static QMutex lock;
+static bool visible = true;
+
#if defined _WIN32
#include "timer.hpp"
+#include "macros.h"
-#include <QMutexLocker>
-
-#include <windows.h>
+static Timer timer;
-constexpr int visible_timeout = 5000;
+constexpr int visible_timeout = 1000;
constexpr int invisible_timeout = 250;
-static Timer timer;
-static QMutex mtx;
-static bool visible = true;
+#include <windows.h>
-never_inline OTR_COMPAT_EXPORT
void set_is_visible(const QWidget& w, bool force)
{
- QMutexLocker l(&mtx);
+ QMutexLocker l(&lock);
- if (!force && timer.elapsed_ms() < (visible ? visible_timeout : invisible_timeout))
+ if (w.isHidden() || w.windowState() & Qt::WindowMinimized)
+ {
+ visible = false;
return;
+ }
- timer.start();
+ {
+ int ndisplays = GetSystemMetrics(SM_CMONITORS);
+ if (ndisplays > 1)
+ {
+ visible = true;
+ return;
+ }
+ }
+
+ HWND hwnd = (HWND)w.winId();
- const HWND id = (HWND) w.winId();
- const QPoint pt = w.mapToGlobal({ 0, 0 });
+ if (!force && timer.elapsed_ms() < (visible ? visible_timeout : invisible_timeout))
+ return;
- const int W = w.width(), H = w.height();
+ timer.start();
- const QPoint points[] =
+ if (RECT r; GetWindowRect(hwnd, &r))
{
- pt,
- pt + QPoint(W - 1, 0),
- pt + QPoint(0, H - 1),
- pt + QPoint(W - 1, H - 1),
- pt + QPoint(W / 2, H / 2),
- };
-
- for (const QPoint& pt : points)
+ const int x = r.left, y = r.top;
+ const int w = r.right - x, h = r.bottom - y;
+
+ const POINT xs[] {
+ { x + w - 1, y + 1 },
+ { x + 1, y + h - 1 },
+ { x + w - 1, y + h - 1 },
+ { x + 1, y + 1 },
+ { x + w/2, y + h/2 },
+ };
+
+ visible = false;
+
+ for (const POINT& pt : xs)
+ if (WindowFromPoint(pt) == hwnd)
+ {
+
+ visible = true;
+ break;
+ }
+ }
+ else
{
- visible = WindowFromPoint({ pt.x(), pt.y() }) == id;
- if (visible)
- break;
+ eval_once(qDebug() << "check-visible: GetWindowRect failed");
+ visible = true;
}
}
-never_inline OTR_COMPAT_EXPORT
-bool check_is_visible()
-{
- QMutexLocker l(&mtx);
-
- return visible;
-}
-
#else
-never_inline OTR_COMPAT_EXPORT
-void set_is_visible(const QWidget&, bool)
+void set_is_visible(const QWidget& w, bool)
{
+ QMutexLocker l(&lock);
+ visible = !(w.isHidden() || w.windowState() & Qt::WindowMinimized);
}
-never_inline OTR_COMPAT_EXPORT
+#endif
+
bool check_is_visible()
{
- return true;
+ QMutexLocker l(&lock);
+ return visible;
}
-#endif
+void force_is_visible(bool value)
+{
+ QMutexLocker l(&lock);
+ visible = value;
+}
diff --git a/compat/check-visible.hpp b/compat/check-visible.hpp
index a0211982..6669b84d 100644
--- a/compat/check-visible.hpp
+++ b/compat/check-visible.hpp
@@ -1,9 +1,15 @@
#pragma once
#include "export.hpp"
-#include "macros.hpp"
+#include "macros.h"
-#include <QWidget>
+class QWidget;
-never_inline OTR_COMPAT_EXPORT void set_is_visible(QWidget const& w, bool force = false);
-never_inline OTR_COMPAT_EXPORT bool check_is_visible();
+never_inline OTR_COMPAT_EXPORT
+void set_is_visible(QWidget const& w, bool force = false);
+
+OTR_COMPAT_EXPORT
+bool check_is_visible();
+
+OTR_COMPAT_EXPORT
+void force_is_visible(bool value);
diff --git a/compat/copyable-mutex.cpp b/compat/copyable-mutex.cpp
deleted file mode 100644
index 7c112c5e..00000000
--- a/compat/copyable-mutex.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#include "copyable-mutex.hpp"
-
-mutex& mutex::operator=(const mutex& datum)
-{
- inner = nullptr;
- inner = std::make_unique<QMutex>(datum->isRecursive()
- ? QMutex::Recursive
- : QMutex::NonRecursive);
- return *this;
-}
-
-mutex::mutex(const mutex& datum)
-{
- *this = datum;
-}
-
-mutex::mutex(mutex::mode m) :
- inner(std::make_unique<QMutex>(static_cast<QMutex::RecursionMode>(int(m))))
-{
-}
-
-QMutex* mutex::operator&() const
-{
- return *this;
-}
-
-QMutex* mutex::operator->() const
-{
- return *this;
-}
-
-mutex::operator QMutex*() const
-{
- return const_cast<QMutex*>(inner.get());
-}
-
diff --git a/compat/copyable-mutex.hpp b/compat/copyable-mutex.hpp
deleted file mode 100644
index af82876d..00000000
--- a/compat/copyable-mutex.hpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#pragma once
-
-#include <memory>
-
-#include <QMutex>
-
-#include "export.hpp"
-
-class OTR_COMPAT_EXPORT mutex
-{
- std::unique_ptr<QMutex> inner;
-
-public:
- enum mode
- {
- recursive = QMutex::Recursive,
- normal = QMutex::NonRecursive,
- };
-
- mutex& operator=(const mutex& datum);
- mutex(const mutex& datum);
- mutex(mode m = normal);
-
- QMutex* operator&() const;
- operator QMutex*() const;
- QMutex* operator->() const;
-};
diff --git a/compat/correlation-calibrator.cpp b/compat/correlation-calibrator.cpp
index 949f10ae..07ef7d3d 100644
--- a/compat/correlation-calibrator.cpp
+++ b/compat/correlation-calibrator.cpp
@@ -2,6 +2,7 @@
#include "variance.hpp"
#include "compat/math.hpp"
#include "compat/meta.hpp"
+#include "compat/macros.h"
#include <cmath>
#include <iterator>
@@ -11,16 +12,9 @@
#define DEBUG_PRINT
#ifdef DEBUG_PRINT
# include <cstdio>
-# include <cwchar>
- using std::fwprintf;
- using std::fflush;
#endif
-using namespace correlation_calibrator_impl;
-
-correlation_calibrator::correlation_calibrator()
-{
-}
+namespace correlation_calibrator_impl {
static constexpr unsigned nbuckets[6] =
{
@@ -44,26 +38,26 @@ static constexpr double spacing[6] =
roll_spacing_in_degrees,
};
-static constexpr wchar_t const* const names[6] {
- L"x", L"y", L"z",
- L"yaw", L"pitch", L"roll",
+static constexpr char const* const names[6] {
+ "x", "y", "z",
+ "yaw", "pitch", "roll",
};
-bool correlation_calibrator::check_buckets(const vec6 &data)
+bool correlation_calibrator::check_buckets(const vec6& data)
{
bool ret = false;
unsigned pos[6];
for (unsigned k = 0; k < 6; k++)
{
- const double val = clamp(data[k], min[k], max[k]);
- pos[k] = (val-min[k])/spacing[k];
+ const double val = std::clamp(data[k], min[k], max[k]);
+ pos[k] = unsigned((val-min[k])/spacing[k]);
if (pos[k] >= nbuckets[k])
{
- once_only(qDebug() << "idx" << k
+ eval_once(qDebug() << "idx" << k
<< "bucket" << (int)pos[k]
- << "outside bounds" << nbuckets[k]);;
+ << "outside bounds" << nbuckets[k]);
return false;
}
@@ -133,18 +127,18 @@ mat66 correlation_calibrator::get_coefficients() const
cs = cs * (1./(data.size() - 1));
#if defined DEBUG_PRINT
- fwprintf(stderr, L"v:change-of h:due-to\n");
- fwprintf(stderr, L"%10s ", L"");
- for (unsigned k = 0; k < 6; k++)
- fwprintf(stderr, L"%10s", names[k]);
- fwprintf(stderr, L"\n");
+ fprintf(stderr, "v:change-of h:due-to\n");
+ fprintf(stderr, "%10s ", "");
+ for (char const* k : names)
+ fprintf(stderr, "%10s", k);
+ fprintf(stderr, "\n");
for (unsigned i = 0; i < 6; i++)
{
- fwprintf(stderr, L"%10s ", names[i]);
+ fprintf(stderr, "%10s ", names[i]);
for (unsigned k = 0; k < 6; k++)
- fwprintf(stderr, L"%10.3f", cs(i, k));
- fwprintf(stderr, L"\n");
+ fprintf(stderr, "%10.3f", cs(i, k));
+ fprintf(stderr, "\n");
}
fflush(stderr);
#endif
@@ -162,3 +156,5 @@ unsigned correlation_calibrator::sample_count() const
{
return data.size();
}
+
+} // ns correlation_calibrator_impl
diff --git a/compat/correlation-calibrator.hpp b/compat/correlation-calibrator.hpp
index b44a2312..44aee537 100644
--- a/compat/correlation-calibrator.hpp
+++ b/compat/correlation-calibrator.hpp
@@ -9,7 +9,7 @@
namespace correlation_calibrator_impl {
-static constexpr inline double min[6] = {
+static constexpr double min[6] = {
-50,
-50,
250,
@@ -19,7 +19,7 @@ static constexpr inline double min[6] = {
-180,
};
-static constexpr inline double max[6] = {
+static constexpr double max[6] = {
50,
50,
250,
@@ -29,18 +29,18 @@ static constexpr inline double max[6] = {
180,
};
-static constexpr inline double yaw_spacing_in_degrees = 1.5;
-static constexpr inline double pitch_spacing_in_degrees = 1;
-static constexpr inline double roll_spacing_in_degrees = 1;
+static constexpr double yaw_spacing_in_degrees = 1.5;
+static constexpr double pitch_spacing_in_degrees = 1;
+static constexpr double roll_spacing_in_degrees = 1;
-static constexpr inline unsigned yaw_nbuckets = 1+ 360./yaw_spacing_in_degrees;
-static constexpr inline unsigned pitch_nbuckets = 1+ 360./pitch_spacing_in_degrees;
-static constexpr inline unsigned roll_nbuckets = 1+ 360./roll_spacing_in_degrees;
+static constexpr unsigned yaw_nbuckets = unsigned(1+ 360./yaw_spacing_in_degrees);
+static constexpr unsigned pitch_nbuckets = unsigned(1+ 360./pitch_spacing_in_degrees);
+static constexpr unsigned roll_nbuckets = unsigned(1+ 360./roll_spacing_in_degrees);
-static constexpr inline double translation_spacing = .25;
-static constexpr inline unsigned x_nbuckets = 1+ (max[0]-min[0])/translation_spacing;
-static constexpr inline unsigned y_nbuckets = 1+ (max[1]-min[1])/translation_spacing;
-static constexpr inline unsigned z_nbuckets = 1+ (max[2]-min[2])/translation_spacing;
+static constexpr double translation_spacing = .25;
+static constexpr unsigned x_nbuckets = unsigned(1+ (max[0]-min[0])/translation_spacing);
+static constexpr unsigned y_nbuckets = unsigned(1+ (max[1]-min[1])/translation_spacing);
+static constexpr unsigned z_nbuckets = unsigned(1+ (max[2]-min[2])/translation_spacing);
using vec6 = Mat<double, 6, 1>;
using mat66 = Mat<double, 6, 6>;
@@ -63,14 +63,14 @@ class OTR_COMPAT_EXPORT correlation_calibrator final
bool check_buckets(const vec6& data);
public:
- correlation_calibrator();
+ correlation_calibrator() = default;
void input(const vec6& data);
mat66 get_coefficients() const;
unsigned sample_count() const;
- static constexpr inline unsigned min_samples = 25;
+ static constexpr unsigned min_samples = 25;
};
} // ns correlation_calibrator_impl
-using correlation_calibrator_impl::correlation_calibrator;
+using correlation_calibrator = correlation_calibrator_impl::correlation_calibrator;
diff --git a/compat/enum-operators.hpp b/compat/enum-operators.hpp
new file mode 100644
index 00000000..e4f81a39
--- /dev/null
+++ b/compat/enum-operators.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <type_traits>
+
+#define OTR_FLAGS_OP2(type, op) \
+ constexpr inline type operator op (type a, type b) \
+ { \
+ using t_ = std::underlying_type_t<type>; \
+ return type(t_((a)) op t_((b))); \
+ } // end
+
+#define OTR_FLAGS_DELETE_SHIFT(type, op) \
+ template<typename u> \
+ type operator op (type, u) = delete // end
+
+#define OTR_FLAGS_OP1(type, op) \
+ constexpr inline type operator op (type x) \
+ { \
+ using t_ = std::underlying_type_t<type>; \
+ return type(op t_((x))); \
+ } // end
+
+#define OTR_FLAGS_ASSIGN_OP(type, op) \
+ constexpr inline type& operator op ## = (type& lhs, type rhs) \
+ { \
+ using t_ = std::underlying_type_t<decltype(rhs)>; \
+ lhs = type(t_((lhs)) op t_((rhs))); \
+ return lhs; \
+ } //end
+
+#define OTR_FLAGS_DELETE_SHIFT_ASSIGN(type, op) \
+ type operator op ## = (type& lhs, type rhs) = delete //end
+
+#define DEFINE_ENUM_OPERATORS(type) \
+ OTR_FLAGS_OP2(type, |) \
+ OTR_FLAGS_OP2(type, &) \
+ OTR_FLAGS_OP2(type, ^) \
+ OTR_FLAGS_OP1(type, ~) \
+ OTR_FLAGS_DELETE_SHIFT(type, <<); \
+ OTR_FLAGS_DELETE_SHIFT(type, >>); \
+ OTR_FLAGS_ASSIGN_OP(type, |) \
+ OTR_FLAGS_ASSIGN_OP(type, &) \
+ OTR_FLAGS_ASSIGN_OP(type, ^) \
+ OTR_FLAGS_DELETE_SHIFT_ASSIGN(type, <<); \
+ OTR_FLAGS_DELETE_SHIFT_ASSIGN(type, >>) // end
diff --git a/compat/euler.cpp b/compat/euler.cpp
index 39d2b116..5d6fb25b 100644
--- a/compat/euler.cpp
+++ b/compat/euler.cpp
@@ -2,38 +2,53 @@
#include "math-imports.hpp"
#include <cmath>
-// rotation order is XYZ
-
namespace euler {
-euler_t OTR_COMPAT_EXPORT rmat_to_euler(const rmat& R)
+Pose_ rmat_to_euler(const rmat& R)
{
- double alpha, beta, gamma;
-
- beta = atan2( -R(2,0), sqrt(R(2,1)*R(2,1) + R(2,2)*R(2,2)) );
- alpha = atan2( R(1,0), R(0,0));
- gamma = atan2( R(2,1), R(2,2));
-
- return { alpha, -beta, gamma };
+ const double cy = sqrt(R(2,2)*R(2, 2) + R(2, 1)*R(2, 1));
+ const bool large_enough = cy > 1e-10;
+ if (large_enough)
+ return {
+ atan2(-R(1, 0), R(0, 0)),
+ atan2(R(2, 0), cy),
+ atan2(-R(2, 1), R(2, 2))
+ };
+ else
+ return {
+ atan2(R(0, 1), R(1, 1)),
+ atan2(R(2, 0), cy),
+ 0
+ };
}
-rmat OTR_COMPAT_EXPORT euler_to_rmat(const euler_t& e)
+// tait-bryan angles, not euler
+rmat euler_to_rmat(const Pose_& input)
{
- const double X = e(2);
- const double Y = -e(1);
- const double Z = e(0);
+ const double H = -input(0);
+ const double P = -input(1);
+ const double B = -input(2);
- const double cx = cos(X);
- const double sx = sin(X);
- const double cy = cos(Y);
- const double sy = sin(Y);
- const double cz = cos(Z);
- const double sz = sin(Z);
+ const auto c1 = cos(H);
+ const auto s1 = sin(H);
+ const auto c2 = cos(P);
+ const auto s2 = sin(P);
+ const auto c3 = cos(B);
+ const auto s3 = sin(B);
return {
- cy*cz, -cy*sz, sy,
- cz*sx*sy + cx*sz, cx*cz - sx*sy*sz, -cy*sx,
- -cx*cz*sy + sx*sz, cz*sx + cx*sy*sz, cx*cy,
+ // z
+ c1*c2,
+ c1*s2*s3 - c3*s1,
+ s1*s3 + c1*c3*s2,
+ // y
+ c2*s1,
+ c1*c3 + s1*s2*s3,
+ c3*s1*s2 - c1*s3,
+ // x
+ -s2,
+ c2*s3,
+ c2*c3
};
}
diff --git a/compat/euler.hpp b/compat/euler.hpp
index d4217a70..b2f10ac0 100644
--- a/compat/euler.hpp
+++ b/compat/euler.hpp
@@ -17,9 +17,9 @@ template<int h_, int w_> using dmat = Mat<double, h_, w_>;
using dvec3 = Mat<double, 3, 1>;
using rmat = dmat<3, 3>;
-using euler_t = dmat<3, 1>;
+using Pose_ = dmat<3, 1>;
-rmat OTR_COMPAT_EXPORT euler_to_rmat(const euler_t& input);
-euler_t OTR_COMPAT_EXPORT rmat_to_euler(const rmat& R);
+rmat OTR_COMPAT_EXPORT euler_to_rmat(const Pose_& input);
+Pose_ OTR_COMPAT_EXPORT rmat_to_euler(const rmat& R);
} // end ns euler
diff --git a/compat/hamilton-tools.cpp b/compat/hamilton-tools.cpp
new file mode 100644
index 00000000..eb0ef984
--- /dev/null
+++ b/compat/hamilton-tools.cpp
@@ -0,0 +1,109 @@
+#include "hamilton-tools.h"
+#include <cmath>
+
+tQuat QuatFromAngleAxe(const double angle, const tVector& axe)
+{
+ double a = TO_RAD * 0.5 * angle;
+ double d = sin(a) / VectorLength(axe);
+ return ( tQuat (
+ axe.v[0] * d,
+ axe.v[1] * d,
+ axe.v[2] * d,
+ cos(a)
+ )
+ );
+}
+
+tQuat QuatMultiply(const tQuat& qL, const tQuat& qR)
+{
+ tQuat Q;
+ Q.x = qL.w*qR.x + qL.x*qR.w + qL.y*qR.z - qL.z*qR.y;
+ Q.y = qL.w*qR.y + qL.y*qR.w + qL.z*qR.x - qL.x*qR.z;
+ Q.z = qL.w*qR.z + qL.z*qR.w + qL.x*qR.y - qL.y*qR.x;
+ Q.w = qL.w*qR.w - qL.x*qR.x - qL.y*qR.y - qL.z*qR.z;
+ return(Q);
+}
+
+tQuat QuatFromYPR(const double YPR[])
+{
+ tQuat Q, Qp, Qy;
+ Q = QuatFromAngleAxe( -YPR[2], {0, 0, 1} ); //Roll, Z axe
+ Qp = QuatFromAngleAxe( -YPR[1], {1, 0, 0} ); //Pitch, X axe
+ Qy = QuatFromAngleAxe( -YPR[0], {0, 1, 0} ); //Yaw, Y axe
+
+ Q = QuatMultiply(Qp, Q);
+ return(QuatMultiply(Qy, Q));
+}
+
+void Normalize(tQuat& Q)
+{
+ double m = sqrt(Q.x*Q.x + Q.y*Q.y + Q.z*Q.z + Q.w*Q.w);
+ if (m > EPSILON)
+ {
+ m = 1 / m;
+ Q.x = Q.x * m;
+ Q.y = Q.y * m;
+ Q.z = Q.z * m;
+ Q.w = Q.w * m;
+ }
+ else Q = tQuat(0, 0, 0, 1);
+}
+
+tQuat Slerp(const tQuat& S, const tQuat& D, const double alpha)
+{
+ // calc cosine of half angle
+ double cosin = S.x*D.x + S.y*D.y + S.z*D.z + S.w*D.w;
+
+ // select nearest rotation direction
+ tQuat Q;
+ if (cosin < 0)
+ {
+ cosin = - cosin;
+ Q.x = - D.x;
+ Q.y = - D.y;
+ Q.z = - D.z;
+ Q.w = - D.w;
+ }
+ else Q = D;
+
+ // calculate coefficients
+ double scale0, scale1;
+ if ((1.0 - cosin) > EPSILON)
+ {
+ double omega = acos(cosin);
+ double sinus = 1 / sin(omega);
+ scale0 = sin((1.0 - alpha) * omega) * sinus;
+ scale1 = sin(alpha * omega)* sinus;
+ }
+ else
+ {
+ scale0 = 1.0 - alpha;
+ scale1 = alpha;
+ }
+
+ Q.x = scale0 * S.x + scale1 * Q.x;
+ Q.y = scale0 * S.y + scale1 * Q.y;
+ Q.z = scale0 * S.z + scale1 * Q.z;
+ Q.w = scale0 * S.w + scale1 * Q.w;
+
+ Normalize(Q);
+
+ return( Q );
+}
+
+void QuatToYPR(const tQuat& Q, double YPR[])
+{
+ const double xx = Q.x * Q.x;
+ const double xy = Q.x * Q.y;
+ const double xz = Q.x * Q.z;
+ const double xw = Q.x * Q.w;
+ const double yy = Q.y * Q.y;
+ const double yz = Q.y * Q.z;
+ const double yw = Q.y * Q.w;
+ const double zz = Q.z * Q.z;
+ const double zw = Q.z * Q.w;
+
+ YPR[0] = TO_DEG * ( -atan2( 2 * ( xz + yw ), 1 - 2 * ( xx + yy ) ));
+ YPR[1] = TO_DEG * ( asin ( 2 * ( yz - xw ) ));
+ YPR[2] = TO_DEG * ( -atan2( 2 * ( xy + zw ), 1 - 2 * ( xx + zz ) ));
+}
diff --git a/compat/hamilton-tools.h b/compat/hamilton-tools.h
new file mode 100644
index 00000000..885e9f79
--- /dev/null
+++ b/compat/hamilton-tools.h
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <algorithm>
+#include "export.hpp"
+#include "compat/math.hpp"
+
+constexpr double TO_RAD = (M_PI / 180);
+constexpr double TO_DEG = (180 / M_PI);
+constexpr double EPSILON = 1e-30;
+
+struct tVector
+{
+ double v[3];
+ tVector(double X = 0, double Y = 0, double Z = 0) {v[0]=X; v[1]=Y; v[2]=Z;}
+ tVector(const double V[]) {v[0]=V[0]; v[1]=V[1]; v[2]=V[2];}
+
+ void operator=(const tVector& other)
+ {
+ std::copy(other.v, other.v + 3, v);
+ }
+ inline const tVector operator+(const tVector& other) const
+ {
+ return tVector(v[0] + other.v[0], v[1] + other.v[1], v[2] + other.v[2]);
+ }
+ void operator+=(const tVector& other)
+ {
+ v[0] += other.v[0];
+ v[1] += other.v[1];
+ v[2] += other.v[2];
+ }
+ const tVector operator-(const tVector& other) const
+ {
+ return tVector(v[0] - other.v[0], v[1] - other.v[1], v[2] - other.v[2]);
+ }
+ void operator-=(const tVector& other)
+ {
+ v[0] -= other.v[0];
+ v[1] -= other.v[1];
+ v[2] -= other.v[2];
+ }
+ const tVector operator*(double scalar) const
+ {
+ return tVector(v[0] * scalar, v[1] * scalar, v[2] * scalar);
+ }
+ void operator*=(double scalar)
+ {
+ v[0] *= scalar;
+ v[1] *= scalar;
+ v[2] *= scalar;
+ }
+ const tVector operator/(double scalar) const
+ {
+ return *this * (1.0 / scalar);
+ }
+ void operator/= (double scalar)
+ {
+ *this *= 1.0 / scalar;
+ }
+};
+
+struct tQuat
+{
+ double x, y, z, w;
+ tQuat(double X = 0, double Y = 0, double Z = 0, double W = 1)
+ {x = X; y = Y; z = Z; w = W;}
+};
+
+inline double VectorLength(const tVector& v) { return sqrt(v.v[0] * v.v[0] + v.v[1] * v.v[1] + v.v[2] * v.v[2]); }
+inline double VectorDistance(const tVector& v1, const tVector& v2) { return VectorLength(v2 - v1); }
+inline tVector Lerp(const tVector& s, const tVector& d, const double alpha) { return s + (d - s) * alpha; }
+tQuat OTR_COMPAT_EXPORT QuatFromYPR(const double YPR[]);
+tQuat OTR_COMPAT_EXPORT QuatMultiply(const tQuat& qL, const tQuat& qR);
+inline tQuat QuatDivide(const tQuat& qL, const tQuat& qR) { return QuatMultiply(qL, tQuat(-qR.x, -qR.y, -qR.z, qR.w)); }
+inline double AngleBetween(const tQuat& S, const tQuat& D) { return TO_DEG * 2 * acos(fabs(S.x * D.x + S.y * D.y + S.z * D.z + S.w * D.w)); }
+tQuat OTR_COMPAT_EXPORT Slerp(const tQuat& S, const tQuat& D, const double alpha);
+void OTR_COMPAT_EXPORT QuatToYPR(const tQuat& Q, double YPR[]);
diff --git a/compat/lang/de_DE.ts b/compat/lang/de_DE.ts
new file mode 100644
index 00000000..1552582e
--- /dev/null
+++ b/compat/lang/de_DE.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+</TS>
diff --git a/compat/lang/zh_CN.ts b/compat/lang/zh_CN.ts
index 6401616d..e5ca8aa9 100644
--- a/compat/lang/zh_CN.ts
+++ b/compat/lang/zh_CN.ts
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
</TS>
diff --git a/compat/library-path.hpp b/compat/library-path.hpp
index e14cf5e0..49e2c414 100644
--- a/compat/library-path.hpp
+++ b/compat/library-path.hpp
@@ -1,4 +1,4 @@
#pragma once
// from build directory
-#include "__opentrack-library-path.h"
+#include "opentrack-library-path.hxx"
diff --git a/compat/linkage-macros.hpp b/compat/linkage-macros.hpp
index 9e71ac8c..f4d0104b 100644
--- a/compat/linkage-macros.hpp
+++ b/compat/linkage-macros.hpp
@@ -1,15 +1,26 @@
#pragma once
-#if defined _MSC_VER
-# define OTR_GENERIC_EXPORT __declspec(dllexport)
-# define OTR_GENERIC_IMPORT __declspec(dllimport)
-#elif defined _WIN32 && !defined(__WINE__)
-# define OTR_GENERIC_EXPORT __attribute__((dllexport, visibility ("default")))
-# define OTR_GENERIC_IMPORT __attribute__((dllimport))
-#else
-# define OTR_GENERIC_EXPORT __attribute__ ((visibility ("default")))
-# define OTR_GENERIC_IMPORT
+#ifndef OTR_GENERIC_EXPORT
+# if defined _MSC_VER
+# define OTR_GENERIC_EXPORT __declspec(dllexport)
+# define OTR_GENERIC_IMPORT __declspec(dllimport)
+# elif defined _WIN32 && !defined __WINE__
+# define OTR_GENERIC_EXPORT __attribute__((dllexport, visibility ("default")))
+# define OTR_GENERIC_IMPORT __attribute__((dllimport))
+# else
+# define OTR_GENERIC_EXPORT __attribute__((visibility ("default")))
+# define OTR_GENERIC_IMPORT
+# endif
+#endif
+
+#if defined __APPLE__ || defined __MINGW32__
+# define OTR_NO_TMPL_INST // link failure on both targets
#endif
-#define OTR_TEMPLATE_EXPORT template class OTR_GENERIC_EXPORT
-#define OTR_TEMPLATE_IMPORT extern template class OTR_GENERIC_IMPORT
+#if defined OTR_NO_TMPL_INST
+# define OTR_TEMPLATE_IMPORT(x)
+# define OTR_TEMPLATE_EXPORT(x)
+#else
+# define OTR_TEMPLATE_IMPORT(x) extern template class OTR_GENERIC_IMPORT x;
+# define OTR_TEMPLATE_EXPORT(x) template class OTR_GENERIC_EXPORT x;
+#endif
diff --git a/compat/macros.h b/compat/macros.h
new file mode 100644
index 00000000..7de7fb86
--- /dev/null
+++ b/compat/macros.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#if defined _MSC_VER
+# define never_inline __declspec(noinline)
+#else
+# define never_inline __attribute__((noinline))
+#endif
+
+#if defined _MSC_VER
+# define force_inline __forceinline
+#else
+# define force_inline __attribute__((always_inline)) inline
+#endif
+
+#if !defined likely
+# if defined __GNUC__
+# define likely(x) __builtin_expect(!!(x),1)
+# define unlikely(x) __builtin_expect(!!(x),0)
+# else
+# define likely(x) (x)
+# define unlikely(x) (x)
+# endif
+#endif
+
+#if defined _MSC_VER
+# define function_name __FUNCSIG__
+#else
+# define function_name __PRETTY_FUNCTION__
+#endif
+
+#ifdef _MSC_VER
+# define unreachable() do { __assume(0); *(volatile int*)nullptr = 0; } while (0) /* NOLINT(clang-analyzer-core.NullDereference) */
+#else
+# define unreachable() do { __builtin_unreachable(); *(volatile int*)nullptr = 0; } while (0) /* NOLINT(clang-analyzer-core.NullDereference) */
+#endif
+
+#ifdef __cplusplus
+# define progn(...) ([&]() -> decltype(auto) { __VA_ARGS__ }())
+# define eval_once2(expr, ctr) eval_once3(expr, ctr)
+# define eval_once3(expr, ctr) ([&] { [[maybe_unused]] static const char init_ ## ctr = ((void)(expr), 0); }())
+# ifdef QT_NO_DEBUG_OUTPUT
+# define eval_once(expr) void()
+# else
+# define eval_once(expr) eval_once2(expr, __COUNTER__)
+# endif
+
+#define OTR_DISABLE_MOVE_COPY(type) \
+ type(const type&) = delete; \
+ type(type&&) = delete; \
+ type& operator=(const type&) = delete; \
+ type& operator=(type&&) = delete
+#endif
+
+#ifdef _MSC_VER
+# define strncasecmp _strnicmp
+# define strcasecmp _stricmp
+#endif
diff --git a/compat/macros.hpp b/compat/macros.hpp
deleted file mode 100644
index c8fbca20..00000000
--- a/compat/macros.hpp
+++ /dev/null
@@ -1,54 +0,0 @@
-#pragma once
-
-#if !defined __WINE__
-# include <QCoreApplication>
-# define otr_tr(...) (QCoreApplication::translate(OTR_MODULE_NAME, __VA_ARGS__))
-# define _(...) (otr_tr(__VA_ARGS__))
-#endif
-
-#if defined _MSC_VER
-#
-# define MEMORY_BARRIER _ReadWriteBarrier()
-#else
-# define MEMORY_BARRIER asm volatile("" ::: "memory")
-#endif
-
-#if defined _MSC_VER
-# define never_inline __declspec(noinline)
-#elif defined __GNUG__
-# define never_inline __attribute__((noinline))
-#else
-# define never_inline
-#endif
-
-#if defined __cplusplus
-# define restrict_ptr __restrict
-#endif
-
-#if defined _MSC_VER
-# define force_inline __forceinline
-#else
-# define force_inline __attribute__((always_inline, gnu_inline)) inline
-#endif
-
-#ifdef Q_CREATOR_RUN
-# define warn_result_unused
-#elif defined _MSC_VER
-# define warn_result_unused _Check_return_
-#else
-# define warn_result_unused __attribute__((warn_unused_result))
-#endif
-
-#if defined __GNUC__
-# define likely(x) __builtin_expect(!!(x),1)
-# define unlikely(x) __builtin_expect(!!(x),0)
-#else
-# define likely(x) (x)
-# define unlikely(x) (x)
-#endif
-
-#if defined _MSC_VER
-# define OTR_FUNNAME (__FUNCSIG__)
-#else
-# define OTR_FUNNAME (__PRETTY_FUNCTION__)
-#endif
diff --git a/compat/math.hpp b/compat/math.hpp
index 5d80dace..c5981cc2 100644
--- a/compat/math.hpp
+++ b/compat/math.hpp
@@ -1,74 +1,16 @@
#pragma once
-#include "macros.hpp"
-
#include <cmath>
#include <type_traits>
-namespace util_detail {
-
-template<typename n>
-inline auto clamp_float(n val, n min_, n max_)
-{
- return std::fmin(std::fmax(val, min_), max_);
-}
-
-template<typename t, typename n>
-struct clamp final
-{
- static inline auto clamp_(const n& val, const n& min_, const n& max_)
- {
- if (unlikely(val > max_))
- return max_;
- if (unlikely(val < min_))
- return min_;
- return val;
- }
-};
-
-template<typename t>
-struct clamp<float, t>
-{
- static inline auto clamp_(float val, float min_, float max_)
- {
- return clamp_float(val, min_, max_);
- }
-};
-
-template<typename t>
-struct clamp<double, t>
-{
- static inline auto clamp_(double val, double min_, double max_)
- {
- return clamp_float(val, min_, max_);
- }
-};
-
-} // ns util_detail
-
-template<typename t, typename u, typename w>
-inline auto clamp(const t& val, const u& min, const w& max)
-{
- using tp = decltype(val + min + max);
- return ::util_detail::clamp<std::decay_t<tp>, tp>::clamp_(val, min, max);
-}
-
template<typename t>
-inline int iround(const t& val)
+inline auto iround(t val) -> std::enable_if_t<std::is_floating_point_v<std::decay_t<t>>, int>
{
- return int(std::round(val));
+ return (int)std::round(val);
}
-template<typename t>
-inline unsigned uround(const t& val)
-{
- return std::round(std::fmax(t(0), val));
-}
-
-#include "macros.hpp"
-
-template <typename T>
-static force_inline constexpr auto signum(T x)
+template <typename t>
+constexpr int signum(const t& x)
{
- return x < T(0) ? -1 : 1;
+ return x < t{0} ? -1 : 1;
}
diff --git a/compat/meta.hpp b/compat/meta.hpp
index 80eca89a..49686996 100644
--- a/compat/meta.hpp
+++ b/compat/meta.hpp
@@ -7,26 +7,12 @@
* copyright notice and this permission notice appear in all copies.
*/
-#include <type_traits>
+namespace meta::detail {
-template<typename t>
-using cv_qualified = std::conditional_t<std::is_fundamental_v<std::decay_t<t>>, std::decay_t<t>, const t&>;
+ template<typename... xs>
+ struct tuple;
-#define progn(...) (([&]() { __VA_ARGS__ })())
-#define prog1(x, ...) (([&]() { auto _ret1324 = (x); do { __VA_ARGS__; } while (0); return _ret1324; })())
-
-#define once_only(...) do { static bool once__ = false; if (!once__) { once__ = true; __VA_ARGS__; } } while(false)
-
-#if 0
-
-#include <tuple>
-#include <cstddef>
-
-namespace meta {
-
-namespace detail {
-
- template<typename x, typename y>
+ template<typename... xs>
struct reverse_;
template<typename x0, typename... xs, template<typename...> class x, typename... ys>
@@ -41,51 +27,97 @@ namespace detail {
using type = x<ys...>;
};
- template<template<typename...> class inst, typename x>
- struct lift_;
+ template<template<typename...> class, typename, typename...> struct lift_;
- template<template<typename...> class inst, template<typename...> class x, typename... xs>
- struct lift_<inst, x<xs...>>
+ template<template<typename...> class to, template<typename...> class from, typename... xs>
+ struct lift_<to, from<xs...>>
{
- using type = inst<xs...>;
+ using type = to<xs...>;
};
- template<typename N, N max, N pos, typename... xs>
- struct index_sequence_
+ template<typename> struct append_helper;
+
+ template<typename, typename> struct cons_;
+
+ template<typename x, template<typename...> class t, typename... xs>
+ struct cons_<x, t<xs...>>
{
- using part = std::integral_constant<N, pos>;
- using type = typename index_sequence_<N, max, pos+1, xs..., part>::type;
+ using type = t<x, xs...>;
};
- template<typename N, N max, typename... xs>
- struct index_sequence_<N, max, max, xs...>
+ template<typename> struct append2;
+
+ template<template<typename...> class t, typename... xs>
+ struct append2<t<xs...>>
{
- using type = std::tuple<xs...>;
+ template<typename> struct append1;
+
+ template<template<typename...> class u, typename... ys>
+ struct append1<u<ys...>>
+ {
+ using type = t<xs..., ys...>;
+ };
};
-} // ns detail
+ template<typename, typename...> struct list__;
+
+ template<typename rest, typename... first>
+ struct list__
+ {
+ template<typename> struct list1;
+
+ template<template<typename...> class t, typename... xs>
+ struct list1<t<xs...>>
+ {
+ using type = t<first..., xs...>;
+ };
+
+ using type = typename list1<rest>::type;
+ };
+
+ template<typename xs, typename ys>
+ struct append_
+ {
+ using t1 = append2<xs>;
+ using type = typename t1::template append1<ys>::type;
+ };
+
+} // ns meta::detail
+
+namespace meta {
+ template<typename... xs>
+ using tuple_ = detail::tuple<xs...>;
+
+ template<typename... xs>
+ using reverse = typename detail::reverse_<detail::tuple<xs...>, detail::tuple<>>::type;
+
+ // the to/from order is awkward but mimics function composition
+ template<template<typename...> class to, typename from>
+ using lift = typename detail::lift_<to, from>::type;
+
+ template<template<typename...> class to, typename from>
+ constexpr inline auto lift_v = detail::lift_<to, from>::type::value;
-template<typename... xs>
-using reverse = typename detail::reverse_<std::tuple<xs...>, std::tuple<>>::type;
+ template<typename x, typename... xs>
+ using first = x;
-template<template<typename...> class inst, class x>
-using lift = typename detail::lift_<inst, x>::type;
+ template<typename x, typename... xs>
+ using rest = detail::tuple<xs...>;
-template<typename x, typename... xs>
-using first = x;
+ template<typename... xs>
+ using butlast = reverse<rest<reverse<xs...>>>;
-template<typename x, typename... xs>
-using rest = std::tuple<xs...>;
+ template<typename... xs>
+ using last = lift<first, reverse<xs...>>;
-template<typename... xs>
-using butlast = reverse<rest<reverse<xs...>>>;
+ template<typename x, typename rest>
+ using cons = typename detail::cons_<x, rest>::type;
-template<typename... xs>
-using last = lift<first, reverse<xs...>>;
+ template<typename xs, typename ys>
+ using append = typename detail::append_<xs, ys>;
-template<std::size_t max>
-using index_sequence = typename detail::index_sequence_<std::size_t, max, std::size_t(0)>::type;
+ template<typename rest, typename... xs>
+ using list_ = typename detail::list__<rest, xs...>;
} // ns meta
-#endif
diff --git a/compat/mutex.cpp b/compat/mutex.cpp
new file mode 100644
index 00000000..664677ea
--- /dev/null
+++ b/compat/mutex.cpp
@@ -0,0 +1,33 @@
+#include "mutex.hpp"
+#include <cstdlib>
+
+mutex& mutex::operator=(const mutex& rhs)
+{
+ if (rhs->isRecursive() != inner.isRecursive())
+ std::abort();
+
+ return *this;
+}
+
+mutex::mutex(const mutex& datum) : mutex{datum.inner.isRecursive() ? Recursive : NonRecursive}
+{
+}
+
+mutex::mutex(RecursionMode m) : inner{m}
+{
+}
+
+QMutex* mutex::operator&() const noexcept
+{
+ return &inner;
+}
+
+mutex::operator QMutex*() const noexcept
+{
+ return &inner;
+}
+
+QMutex* mutex::operator->() const noexcept
+{
+ return &inner;
+}
diff --git a/compat/mutex.hpp b/compat/mutex.hpp
new file mode 100644
index 00000000..54758a08
--- /dev/null
+++ b/compat/mutex.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <QMutex>
+
+#include "export.hpp"
+
+class OTR_COMPAT_EXPORT mutex
+{
+ mutable QMutex inner;
+
+public:
+ using RecursionMode = QMutex::RecursionMode;
+ static constexpr RecursionMode Recursive = RecursionMode::Recursive;
+ static constexpr RecursionMode NonRecursive = RecursionMode::NonRecursive;
+
+ mutex& operator=(const mutex& datum);
+ mutex(const mutex& datum);
+ explicit mutex(RecursionMode m);
+ mutex() : mutex{NonRecursive} {}
+
+ QMutex* operator&() const noexcept;
+ explicit operator QMutex*() const noexcept;
+ QMutex* operator->() const noexcept;
+};
diff --git a/compat/process-list.hpp b/compat/process-list.hpp
index 10613791..39e12603 100644
--- a/compat/process-list.hpp
+++ b/compat/process-list.hpp
@@ -52,7 +52,7 @@ static QStringList get_all_executable_names()
template<typename = void>
static QStringList get_all_executable_names()
{
- QStringList ret;
+ QStringList ret; ret.reserve(2048);
std::vector<int> vec;
while (true)
@@ -103,7 +103,7 @@ static QStringList get_all_executable_names()
if (idx != -1)
{
QString tmp = cmdline[0].mid(idx+1);
- if (cmdline.size() > 1 && (tmp == "wine.bin" || tmp == "wine"))
+ if (cmdline.size() > 1 && (tmp == QStringLiteral("wine.bin") || tmp == QStringLiteral("wine")))
{
idx = cmdline[1].lastIndexOf('/');
if (idx == -1)
@@ -129,14 +129,68 @@ static QStringList get_all_executable_names()
}
}
-#elif defined __linux
+#elif defined __linux__
+
+#include <cerrno>
+
+#ifdef OTR_HAS_LIBPROC2
+#include <libproc2/pids.h>
+template<typename = void>
+QStringList get_all_executable_names()
+{
+ QStringList ret; ret.reserve(2048);
+ enum pids_item items[] = { PIDS_ID_PID, PIDS_CMD, PIDS_CMDLINE_V };
+
+ enum rel_items { rel_pid, rel_cmd, rel_cmdline };
+ struct pids_info *info = NULL;
+ struct pids_stack *stack;
+ QString tmp; tmp.reserve(255);
+
+ procps_pids_new(&info, items, 3);
+ // procps-ng version 4.0.5 removed an unused argument in PIDS_VAL() macro.
+ // cf. https://gitlab.com/procps-ng/procps/-/commit/967fdcfb06e20aad0f3
+
+ // Although the emitted machine code is identical, backward API
+ // compatibility was silently broken in the patch with no upgrade path
+ // (e.g. deprecating PIDS_VAL() while introducing PIDS_VAL2()).
+
+ // Unfortunately, procps-ng doesn't include a #define for identifying its
+ // version. For these reasons the code below depends on undocumented ABI
+ // compatibility between procps-ng versions.. -sh 20241226
+
+#define OPENTRACK_PIDS_VAL(i, type, stack) stack->head[i].result.type
+
+ while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY)))
+ {
+ char **p_cmdline = OPENTRACK_PIDS_VAL(rel_cmdline, strv, stack);
+
+ // note, wine sets argv[0] so no parsing like in OSX case
+ if (p_cmdline && p_cmdline[0] && p_cmdline[0][0] &&
+ !(p_cmdline[0][0] == '-' && !p_cmdline[0][1]))
+ {
+ tmp = QString{p_cmdline[0]};
+ const int idx = std::max(tmp.lastIndexOf('\\'), tmp.lastIndexOf('/'));
+ if (idx != -1)
+ tmp = tmp.mid(idx+1);
+ //qDebug() << "procps" << tmp;
+ ret.append(tmp);
+ }
+ }
+ //qDebug() << "-- procps end";
+
+ procps_pids_unref(&info);
+
+ return ret;
+}
+#else
#include <proc/readproc.h>
#include <cerrno>
+
template<typename = void>
-static QStringList get_all_executable_names()
+QStringList get_all_executable_names()
{
- QStringList ret;
+ QStringList ret; ret.reserve(2048);
proc_t** procs = readproctab(PROC_FILLCOM);
if (procs == nullptr)
{
@@ -159,6 +213,7 @@ static QStringList get_all_executable_names()
free(procs);
return ret;
}
+#endif
#else
template<typename = void>
diff --git a/compat/qhash.hpp b/compat/qhash.hpp
new file mode 100644
index 00000000..5286e97e
--- /dev/null
+++ b/compat/qhash.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <functional>
+
+#include <QtGlobal>
+#include <QString>
+#include <QHashFunctions>
+
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
+
+#include <cstdlib>
+
+namespace std {
+template<> struct hash<QString>
+{
+ using argument_type = QString;
+ using result_type = std::size_t;
+
+ std::size_t operator()(const QString& value) const noexcept
+ {
+ return (std::size_t) qHash(value);
+ }
+};
+}
+
+#endif
diff --git a/compat/qt-dpi.hpp b/compat/qt-dpi.hpp
new file mode 100644
index 00000000..fc17a062
--- /dev/null
+++ b/compat/qt-dpi.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <algorithm>
+#include <QWidget>
+
+static inline double screen_dpi(const QPaintDevice* widget)
+{
+ const auto& x = *widget;
+#ifdef _WIN32
+ return std::max(x.devicePixelRatioF(), 1.);
+#else
+ return std::max(std::max(x.logicalDpiX()/(double)x.physicalDpiX(), x.devicePixelRatioF()), 1.);
+#endif
+}
+
+template<typename self>
+struct screen_dpi_mixin
+{
+protected:
+ double screen_dpi() const
+ {
+ return ::screen_dpi(static_cast<const self*>(this));
+ }
+};
diff --git a/compat/qt-signal.cpp b/compat/qt-signal.cpp
new file mode 100644
index 00000000..8c23866b
--- /dev/null
+++ b/compat/qt-signal.cpp
@@ -0,0 +1,7 @@
+#define OTR_GENERATE_SIGNAL3(type) void sig_##type::operator()(const type& x) const { notify(x); }
+#include "qt-signal.hpp"
+namespace _qt_sig_impl {
+
+sig_void::sig_void(QObject* parent) : QObject(parent) {}
+
+}
diff --git a/compat/qt-signal.hpp b/compat/qt-signal.hpp
new file mode 100644
index 00000000..f74de642
--- /dev/null
+++ b/compat/qt-signal.hpp
@@ -0,0 +1,72 @@
+#pragma once
+
+// this is to avoid dealing with QMetaObject for the time being -sh 20190203
+
+#include "export.hpp"
+namespace options { class slider_value; }
+#include <QObject>
+#include <QList>
+
+namespace _qt_sig_impl {
+
+template<typename t> struct sig;
+
+class OTR_COMPAT_EXPORT sig_void final : public QObject
+{
+ Q_OBJECT
+
+public:
+ template<typename t, typename F>
+ sig_void(t* datum, F&& f, Qt::ConnectionType conntype = Qt::AutoConnection) : QObject(datum)
+ {
+ connect(this, &sig_void::notify, datum, f, conntype);
+ }
+ explicit sig_void(QObject* parent = nullptr);
+ void operator()() const { notify(); }
+
+signals:
+ void notify() const;
+};
+
+template<> struct sig<void> { using t = sig_void; };
+
+#ifndef OTR_GENERATE_SIGNAL3
+# define OTR_GENERATE_SIGNAL3(x)
+#endif
+#define OTR_GENERATE_SIGNAL2(type) \
+ class OTR_COMPAT_EXPORT sig_##type final : public QObject \
+ { \
+ Q_OBJECT \
+ public: \
+ explicit sig_##type(QObject* parent = nullptr) : QObject(parent) {} \
+ void operator()(const type& x) const; \
+ Q_SIGNALS: \
+ void notify(const type& x) const; \
+ }; \
+ OTR_GENERATE_SIGNAL3(type)
+
+# define OTR_GENERATE_SIGNAL(type) \
+ OTR_GENERATE_SIGNAL2(type); \
+ using qlist##type = QList<type>; \
+ OTR_GENERATE_SIGNAL2(qlist##type); \
+ template<> struct sig<type> { using t = sig_##type; }; \
+ template<> struct sig<qlist##type> { using t = qlist##type; }
+
+using slider_value = options::slider_value;
+
+OTR_GENERATE_SIGNAL(int);
+OTR_GENERATE_SIGNAL(double);
+OTR_GENERATE_SIGNAL(float);
+OTR_GENERATE_SIGNAL(bool);
+OTR_GENERATE_SIGNAL(QString);
+OTR_GENERATE_SIGNAL(slider_value);
+OTR_GENERATE_SIGNAL(QPointF);
+OTR_GENERATE_SIGNAL(QVariant);
+
+} // namespace _qt_sig_impl
+
+#undef OTR_GENERATE_SIGNAL2
+#undef OTR_GENERATE_SIGNAL
+
+template<typename t> using qt_signal = typename _qt_sig_impl::sig<t>::t;
+
diff --git a/compat/run-in-thread.hpp b/compat/run-in-thread.hpp
index b425532e..c552c600 100644
--- a/compat/run-in-thread.hpp
+++ b/compat/run-in-thread.hpp
@@ -7,9 +7,6 @@
* copyright notice and this permission notice appear in all copies.
*/
-#include "macros.hpp"
-
-#include <cassert>
#include <thread>
#include <condition_variable>
#include <utility>
@@ -17,99 +14,56 @@
#include <QObject>
#include <QThread>
-namespace qt_impl_detail {
-
-template<typename t>
-struct run_in_thread_traits
+namespace impl_run_in_thread {
+struct semaphore final
{
- using type = t;
- using ret_type = t;
- static inline void assign(t& lvalue, const t& rvalue) { lvalue = rvalue; }
- static inline t pass(const t& val) { return val; }
- template<typename F> static inline t call(F&& fun) { return std::move(fun()); }
-};
+ using lock_guard = std::unique_lock<std::mutex>;
+ std::mutex mtx;
+ std::condition_variable cvar;
+ bool flag = false;
-template<typename u>
-struct run_in_thread_traits<u&&>
-{
- using t = typename std::remove_reference<u>::type;
- using type = t;
- using ret_type = u;
- static inline void assign(t& lvalue, t&& rvalue) { lvalue = rvalue; }
- static inline t&& pass(t&& val) { return val; }
- template<typename F> static inline t&& call(F&& fun) { return std::move(fun()); }
-};
+ semaphore() = default;
-template<>
-struct run_in_thread_traits<void>
-{
- using type = unsigned char;
- using ret_type = void;
- static inline void assign(unsigned char&, unsigned char&&) {}
- static inline void pass(type&&) {}
- template<typename F> static type call(F&& fun) { fun(); return type(0); }
-};
+ void wait()
+ {
+ lock_guard guard(mtx);
+ while (!flag)
+ cvar.wait(guard);
+ }
+ void notify()
+ {
+ lock_guard guard(mtx);
+ flag = true;
+ cvar.notify_one();
+ }
+};
}
template<typename F>
-auto never_inline
-run_in_thread_sync(QObject* obj, F&& fun)
- -> typename qt_impl_detail::run_in_thread_traits<decltype(fun())>::ret_type
+void run_in_thread_sync(QObject* obj, F&& fun)
{
- using lock_guard = std::unique_lock<std::mutex>;
+ if (obj->thread() == QThread::currentThread())
+ return (void)fun();
- using traits = qt_impl_detail::run_in_thread_traits<decltype(fun())>;
-
- typename traits::type ret;
-
- struct semaphore final
- {
- std::mutex mtx;
- std::condition_variable cvar;
- bool flag;
-
- semaphore() : flag(false) {}
-
- void wait()
- {
- lock_guard guard(mtx);
- while (!flag)
- cvar.wait(guard);
- }
-
- void notify()
- {
- lock_guard guard(mtx);
- flag = true;
- cvar.notify_one();
- }
- };
-
- semaphore sem;
+ impl_run_in_thread::semaphore sem;
{
QObject src;
- QObject::connect(&src,
- &QObject::destroyed,
- obj,
- [&]() {
- traits::assign(ret, traits::call(fun));
- sem.notify();
- },
- Qt::AutoConnection);
+ QObject::connect(&src, &QObject::destroyed,
+ obj, [&] { fun(); sem.notify(); },
+ Qt::QueuedConnection);
}
sem.wait();
- return traits::pass(std::move(ret));
}
template<typename F>
void run_in_thread_async(QObject* obj, F&& fun)
{
+ if (obj->thread() == QThread::currentThread())
+ return (void)fun();
+
QObject src;
- QThread* t(obj->thread());
- assert(t);
- src.moveToThread(t);
- QObject::connect(&src, &QObject::destroyed, obj, std::move(fun), Qt::AutoConnection);
+ QObject::connect(&src, &QObject::destroyed, obj, std::forward<F>(fun), Qt::QueuedConnection);
}
diff --git a/compat/shm.cpp b/compat/shm.cpp
index 4bd89d35..f59469dc 100644
--- a/compat/shm.cpp
+++ b/compat/shm.cpp
@@ -10,130 +10,36 @@
#if defined _WIN32
#include <cstring>
-#include <stdio.h>
+#include <cstdio>
#include <accctrl.h>
#include <aclapi.h>
-#if !defined __WINE__
+#ifdef QT_CORE_LIB
# include <QDebug>
+# define warn(str, ...) (qDebug() << "shm:" str ": " << __VA_ARGS__)
+#else
+# define warn(str, ...) (void)0
#endif
-struct secattr final
-{
- bool success;
- SECURITY_DESCRIPTOR* pSD;
- SECURITY_ATTRIBUTES attrs;
- PSID pEveryoneSID;
- PACL pACL;
-
- void cleanup();
- secattr(DWORD perms);
- ~secattr();
-};
-
-void secattr::cleanup()
-{
- if (pEveryoneSID)
- FreeSid(pEveryoneSID);
- if (pACL)
- LocalFree(pACL);
- if (pSD)
- LocalFree(pSD);
- success = false;
- pSD = nullptr;
- pEveryoneSID = nullptr;
- pACL = nullptr;
-}
-
-secattr::secattr(DWORD perms) : success(true), pSD(nullptr), pEveryoneSID(nullptr), pACL(nullptr)
-{
- SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
- EXPLICIT_ACCESS ea;
-
- if(!AllocateAndInitializeSid(&SIDAuthWorld, 1,
- SECURITY_WORLD_RID,
- 0, 0, 0, 0, 0, 0, 0,
- &pEveryoneSID))
- {
- fprintf(stderr, "AllocateAndInitializeSid: %d\n", (int) GetLastError());
- goto cleanup;
- }
-
- memset(&ea, 0, sizeof(ea));
-
- ea.grfAccessPermissions = perms;
- ea.grfAccessMode = SET_ACCESS;
- ea.grfInheritance = NO_INHERITANCE;
- ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
- ea.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
- ea.Trustee.ptstrName = (LPTSTR) pEveryoneSID;
-
- if (SetEntriesInAcl(1, &ea, NULL, &pACL) != ERROR_SUCCESS)
- {
- fprintf(stderr, "SetEntriesInAcl: %d\n", (int) GetLastError());
- goto cleanup;
- }
-
- pSD = (SECURITY_DESCRIPTOR*) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
- if (pSD == nullptr)
- {
- fprintf(stderr, "LocalAlloc: %d\n", (int) GetLastError());
- goto cleanup;
- }
-
- if (!InitializeSecurityDescriptor(pSD,
- SECURITY_DESCRIPTOR_REVISION))
- {
- fprintf(stderr, "InitializeSecurityDescriptor: %d\n", (int) GetLastError());
- goto cleanup;
- }
-
- if (!SetSecurityDescriptorDacl(pSD,
- TRUE,
- pACL,
- FALSE))
- {
- fprintf(stderr, "SetSecurityDescriptorDacl: %d\n", (int) GetLastError());
- goto cleanup;
- }
-
- attrs.bInheritHandle = false;
- attrs.lpSecurityDescriptor = pSD;
- attrs.nLength = sizeof(SECURITY_ATTRIBUTES);
-
- return;
-cleanup:
- cleanup();
-}
-
-secattr::~secattr()
-{
- cleanup();
-}
-
shm_wrapper::shm_wrapper(const char* shm_name, const char* mutex_name, int map_size)
{
- secattr sa(GENERIC_ALL|SYNCHRONIZE);
-
if (mutex_name == nullptr)
mutex = nullptr;
else
{
- mutex = CreateMutexA(sa.success ? &sa.attrs : nullptr, false, mutex_name);
+ mutex = CreateMutexA(nullptr, false, mutex_name);
if (!mutex)
{
- #if !defined __WINE__
- qDebug() << "CreateMutexA:" << (int) GetLastError();
- #endif
+ warn("CreateMutexA", (int) GetLastError());
return;
}
}
mapped_file = CreateFileMappingA(
INVALID_HANDLE_VALUE,
- sa.success ? &sa.attrs : nullptr,
+ nullptr,
PAGE_READWRITE,
0,
map_size,
@@ -141,9 +47,7 @@ shm_wrapper::shm_wrapper(const char* shm_name, const char* mutex_name, int map_s
if (!mapped_file)
{
-#if !defined __WINE__
- qDebug() << "CreateFileMappingA:", (int) GetLastError();
-#endif
+ warn("CreateFileMappingA", (int) GetLastError());
return;
}
@@ -155,19 +59,15 @@ shm_wrapper::shm_wrapper(const char* shm_name, const char* mutex_name, int map_s
map_size);
if (!mem)
- {
-#if !defined __WINE__
- qDebug() << "MapViewOfFile:" << (int) GetLastError();
-#endif
- }
+ warn("MapViewOfFile:", (int) GetLastError());
}
shm_wrapper::~shm_wrapper()
{
- if(!UnmapViewOfFile(mem))
+ if (mem && !UnmapViewOfFile(mem))
goto fail;
- if (!CloseHandle(mapped_file))
+ if (mapped_file && !CloseHandle(mapped_file))
goto fail;
if (mutex && !CloseHandle(mutex))
@@ -176,10 +76,7 @@ shm_wrapper::~shm_wrapper()
return;
fail:
- (void)0;
-#if !defined __WINE__
- qDebug() << "failed to close mapping";
-#endif
+ warn("failed to close mapping", (int) GetLastError());
}
bool shm_wrapper::lock()
@@ -209,7 +106,7 @@ shm_wrapper::shm_wrapper(const char *shm_name, const char* /*mutex_name*/, int m
strcat(filename, shm_name);
fd = shm_open(filename, O_RDWR | O_CREAT, 0600);
(void) ftruncate(fd, map_size);
- mem = mmap(NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)0);
+ mem = mmap(nullptr, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)0);
}
shm_wrapper::~shm_wrapper()
@@ -234,7 +131,7 @@ bool shm_wrapper::success()
#ifndef _WIN32
return mem != (void*) -1;
#else
- return mem != NULL;
+ return mem != nullptr;
#endif
}
diff --git a/compat/shm.h b/compat/shm.h
index 5ea6c80a..5036b335 100644
--- a/compat/shm.h
+++ b/compat/shm.h
@@ -19,8 +19,8 @@
#include <sys/types.h>
#endif
-#include "macros.hpp"
#include "export.hpp"
+#include "macros.h"
class OTR_COMPAT_EXPORT shm_wrapper final
{
@@ -28,14 +28,17 @@ class OTR_COMPAT_EXPORT shm_wrapper final
#if defined(_WIN32)
HANDLE mutex, mapped_file;
#else
- int fd, size;
+ int fd;
+ unsigned size;
#endif
public:
- never_inline shm_wrapper(const char *shm_name, const char *mutex_name, int map_size);
- never_inline ~shm_wrapper();
- never_inline bool lock();
- never_inline bool unlock();
- never_inline bool success();
+ shm_wrapper(const char *shm_name, const char *mutex_name, int map_size);
+ ~shm_wrapper();
+ bool lock();
+ bool unlock();
+ bool success();
inline void* ptr() { return mem; }
+
+ OTR_DISABLE_MOVE_COPY(shm_wrapper);
};
diff --git a/compat/simple-mat.hpp b/compat/simple-mat.hpp
index c8c9f48b..e697f76a 100644
--- a/compat/simple-mat.hpp
+++ b/compat/simple-mat.hpp
@@ -8,13 +8,12 @@
#pragma once
-#include "export.hpp"
-
+#include <cstddef>
#include <type_traits>
#include <utility>
#include <cmath>
-namespace simple_mat_detail {
+namespace simple_mat {
// last param to fool SFINAE into overloading
template<int i, int j, int>
struct equals
@@ -51,81 +50,83 @@ namespace simple_mat_detail {
enum { Q = a == 1 ? 3 : 1 };
};
- template<typename num, int h, int w, typename...ts>
+ template<typename, int h, int w, typename...ts>
struct is_arglist_correct
{
enum { value = h * w == sizeof...(ts) };
};
-template<typename num, int h_, int w_>
+template<typename num, int H, int W>
class Mat
{
- static_assert(h_ > 0 && w_ > 0, "must have positive mat dimensions");
- num data[h_][w_];
+ static_assert(H > 0 && W > 0, "must have positive mat dimensions");
+ num data[H][W];
public:
- template<int Q = w_> std::enable_if_t<equals<Q, 1, 0>::value, num>
- constexpr inline operator()(unsigned i) const& { return data[i][0]; }
+ // parameters W and H are rebound so that SFINAE occurs
+ // removing them causes a compile-time error -sh 20150811
+
+ template<typename t, int Q = W> std::enable_if_t<equals<Q, 1, 0>::value, num>
+ constexpr inline operator()(t i) const& { return data[(unsigned)i][0]; }
- template<int P = h_> std::enable_if_t<equals<P, 1, 1>::value, num>
- constexpr inline operator()(unsigned i) const& { return data[0][i]; }
+ template<typename t, int P = H> std::enable_if_t<equals<P, 1, 1>::value, num>
+ constexpr inline operator()(t i) const& { return data[0][(unsigned)i]; }
- template<int Q = w_> std::enable_if_t<equals<Q, 1, 2>::value, num&>
- constexpr inline operator()(unsigned i) & { return data[i][0]; }
+ template<typename t, int Q = W> std::enable_if_t<equals<Q, 1, 2>::value, num&>
+ constexpr inline operator()(t i) & { return data[(unsigned)i][0]; }
- template<int P = h_> std::enable_if_t<equals<P, 1, 3>::value, num&>
- constexpr inline operator()(unsigned i) & { return data[0][i]; }
+ template<typename t, int P = H> std::enable_if_t<equals<P, 1, 3>::value, num&>
+ constexpr inline operator()(t i) & { return data[0][(unsigned)i]; }
-#define OPENTRACK_ASSERT_SWIZZLE static_assert(P == h_ && Q == w_, "")
+#define OTR_MAT_ASSERT_SWIZZLE static_assert(P == H && Q == W)
// const variants
- template<int P = h_, int Q = w_> std::enable_if_t<maybe_add_swizzle<P, Q, 1, 4>::value, num>
- constexpr inline x() const& { OPENTRACK_ASSERT_SWIZZLE; return operator()(0); }
+ template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 1, 4>::value, num>
+ constexpr inline x() const& { OTR_MAT_ASSERT_SWIZZLE; return operator()(0); }
- template<int P = h_, int Q = w_> std::enable_if_t<maybe_add_swizzle<P, Q, 2, 4>::value, num>
- constexpr inline y() const& { OPENTRACK_ASSERT_SWIZZLE; return operator()(1); }
+ template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 2, 4>::value, num>
+ constexpr inline y() const& { OTR_MAT_ASSERT_SWIZZLE; return operator()(1); }
- template<int P = h_, int Q = w_> std::enable_if_t<maybe_add_swizzle<P, Q, 3, 4>::value, num>
- constexpr inline z() const& { OPENTRACK_ASSERT_SWIZZLE; return operator()(2); }
+ template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 3, 4>::value, num>
+ constexpr inline z() const& { OTR_MAT_ASSERT_SWIZZLE; return operator()(2); }
- template<int P = h_, int Q = w_> std::enable_if_t<maybe_add_swizzle<P, Q, 4, 4>::value, num>
- constexpr inline w() const& { OPENTRACK_ASSERT_SWIZZLE; return operator()(3); }
+ template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 4, 4>::value, num>
+ constexpr inline w() const& { OTR_MAT_ASSERT_SWIZZLE; return operator()(3); }
// mutable variants
- template<int P = h_, int Q = w_> std::enable_if_t<maybe_add_swizzle<P, Q, 1, 4>::value, num&>
- constexpr inline x() & { OPENTRACK_ASSERT_SWIZZLE; return operator()(0); }
+ template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 1, 4>::value, num&>
+ constexpr inline x() & { OTR_MAT_ASSERT_SWIZZLE; return operator()(0); }
- template<int P = h_, int Q = w_> std::enable_if_t<maybe_add_swizzle<P, Q, 2, 4>::value, num&>
- constexpr inline y() & { OPENTRACK_ASSERT_SWIZZLE; return operator()(1); }
+ template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 2, 4>::value, num&>
+ constexpr inline y() & { OTR_MAT_ASSERT_SWIZZLE; return operator()(1); }
- template<int P = h_, int Q = w_> std::enable_if_t<maybe_add_swizzle<P, Q, 3, 4>::value, num&>
- constexpr inline z() & { OPENTRACK_ASSERT_SWIZZLE; return operator()(2); }
+ template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 3, 4>::value, num&>
+ constexpr inline z() & { OTR_MAT_ASSERT_SWIZZLE; return operator()(2); }
- template<int P = h_, int Q = w_> std::enable_if_t<maybe_add_swizzle<P, Q, 4, 4>::value, num&>
- constexpr inline w() & { OPENTRACK_ASSERT_SWIZZLE; return operator()(3); }
+ template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 4, 4>::value, num&>
+ constexpr inline w() & { OTR_MAT_ASSERT_SWIZZLE; return operator()(3); }
- // parameters w_ and h_ are rebound so that SFINAE occurs
- // removing them causes a compile-time error -sh 20150811
-
- template<int P = h_, int Q = w_>
- std::enable_if_t<is_vector<P, Q>::value, num>
- norm() const
+ template<int P = H, int Q = W>
+ constexpr auto norm_squared() const -> std::enable_if_t<is_vector<P, Q>::value, num>
{
- static_assert(P == h_ && Q == w_, "");
+ static_assert(P == H && Q == W);
const num val = dot(*this);
+ constexpr num eps = num(1e-4);
- if (val < num(1e-4))
+ if (val < eps)
return num(0);
else
- return std::sqrt(val);
+ return val;
}
- template<int R, int S, int P = h_, int Q = w_>
+ inline auto norm() const { return num(std::sqrt(norm_squared())); }
+
+ template<int R, int S, int P = H, int Q = W>
std::enable_if_t<is_vector_pair<R, S, P, Q>::value, num>
constexpr dot(const Mat<num, R, S>& p2) const
{
- static_assert(P == h_ && Q == w_, "");
+ static_assert(P == H && Q == W);
num ret = 0;
constexpr int len = vector_len<R, S>::value;
@@ -134,166 +135,201 @@ public:
return ret;
}
- template<int R, int S, int P = h_, int Q = w_>
+ template<int R, int S, int P = H, int Q = W>
std::enable_if_t<is_dim3<P, Q, R, S>::value, Mat<num, is_dim3<P, Q, R, S>::P, is_dim3<P, Q, R, S>::Q>>
constexpr cross(const Mat<num, R, S>& b) const
{
- static_assert(P == h_ && Q == w_, "");
- auto& a = *this;
+ static_assert(P == H && Q == W);
+ const auto& a = *this;
return Mat<num, R, S>(a.y()*b.z() - a.z()*b.y(),
a.z()*b.x() - a.x()*b.z(),
a.x()*b.y() - a.y()*b.x());
}
- constexpr Mat<num, h_, w_> operator+(const Mat<num, h_, w_>& other) const
+ constexpr Mat<num, H, W> operator+(const Mat<num, H, W>& other) const
{
- Mat<num, h_, w_> ret;
- for (int j = 0; j < h_; j++)
- for (int i = 0; i < w_; i++)
+ Mat<num, H, W> ret;
+ for (int j = 0; j < H; j++)
+ for (int i = 0; i < W; i++)
ret(j, i) = data[j][i] + other.data[j][i];
return ret;
}
- constexpr Mat<num, h_, w_> operator-(const Mat<num, h_, w_>& other) const
+ constexpr Mat<num, H, W> operator-(const Mat<num, H, W>& other) const
{
- Mat<num, h_, w_> ret;
- for (int j = 0; j < h_; j++)
- for (int i = 0; i < w_; i++)
+ Mat<num, H, W> ret;
+ for (int j = 0; j < H; j++)
+ for (int i = 0; i < W; i++)
ret(j, i) = data[j][i] - other.data[j][i];
return ret;
}
- constexpr Mat<num, h_, w_> operator+(const num other) const
+ constexpr Mat<num, H, W> operator+(const num other) const
{
- Mat<num, h_, w_> ret;
- for (int j = 0; j < h_; j++)
- for (int i = 0; i < w_; i++)
+ Mat<num, H, W> ret;
+ for (int j = 0; j < H; j++)
+ for (int i = 0; i < W; i++)
ret(j, i) = data[j][i] + other;
return ret;
}
- constexpr Mat<num, h_, w_> operator-(const num other) const
+ constexpr Mat<num, H, W> operator-(const num other) const
{
- Mat<num, h_, w_> ret;
- for (int j = 0; j < h_; j++)
- for (int i = 0; i < w_; i++)
+ Mat<num, H, W> ret;
+ for (int j = 0; j < H; j++)
+ for (int i = 0; i < W; i++)
ret(j, i) = data[j][i] - other;
return ret;
}
template<int p>
- constexpr Mat<num, h_, p> operator*(const Mat<num, w_, p>& other) const
+ constexpr Mat<num, H, p> operator*(const Mat<num, W, p>& other) const
{
- Mat<num, h_, p> ret;
- for (int k = 0; k < h_; k++)
+ Mat<num, H, p> ret;
+ for (int k = 0; k < H; k++)
for (int i = 0; i < p; i++)
{
ret(k, i) = 0;
- for (int j = 0; j < w_; j++)
+ for (int j = 0; j < W; j++)
ret(k, i) += data[k][j] * other(j, i);
}
return ret;
}
- constexpr Mat<num, h_, w_> mult_elementwise(const Mat<num, h_, w_>& other) const&
+ constexpr Mat<num, H, W> mult_elementwise(const Mat<num, H, W>& other) const&
{
- Mat<num, h_, w_> ret;
+ Mat<num, H, W> ret;
- for (unsigned j = 0; j < h_; j++)
- for (unsigned i = 0; i < w_; i++)
+ for (unsigned j = 0; j < H; j++)
+ for (unsigned i = 0; i < W; i++)
ret(j, i) = data[j][i] * other.data[j][i];
return ret;
}
template<typename t, typename u>
- constexpr inline num operator()(t j, u i) const& { return data[(int) j][(int) i]; }
+ constexpr inline num operator()(t j, u i) const& { return data[(unsigned)j][(unsigned)i]; }
template<typename t, typename u>
- constexpr inline num& operator()(t j, u i) & { return data[(int) j][(int) i]; }
+ constexpr inline num& operator()(t j, u i) & { return data[(unsigned)j][(unsigned)i]; }
-#ifdef __GNUG__
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wmissing-braces"
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wmissing-braces"
#endif
- template<typename... ts, int h__ = h_, int w__ = w_,
- typename = std::enable_if_t<is_arglist_correct<num, h__, w__, ts...>::value>>
+ template<typename... ts, int h2 = H, int w2 = W,
+ typename = std::enable_if_t<is_arglist_correct<num, h2, w2, ts...>::value>>
constexpr Mat(const ts... xs) : data{static_cast<num>(xs)...}
{
- static_assert(h__ == h_ && w__ == w_, "");
+ static_assert(h2 == H && w2 == W);
}
-#ifdef __GNUG__
-# pragma GCC diagnostic pop
+#ifdef __clang__
+# pragma clang diagnostic pop
#endif
constexpr Mat()
{
- for (int j = 0; j < h_; j++)
- for (int i = 0; i < w_; i++)
+ for (int j = 0; j < H; j++)
+ for (int i = 0; i < W; i++)
data[j][i] = num(0);
}
- Mat(const num* mem)
+ constexpr Mat(const num* mem)
{
- for (int j = 0; j < h_; j++)
- for (int i = 0; i < w_; i++)
- data[j][i] = mem[i*h_+j];
+ for (int j = 0; j < H; j++)
+ for (int i = 0; i < W; i++)
+ data[j][i] = mem[i*H+j];
}
- operator num*() { return reinterpret_cast<num*>(data); }
- operator const num*() const { return reinterpret_cast<const num*>(data); }
+ constexpr Mat(const Mat<num, H, W>& x)
+ {
+ for (int j = 0; j < H; j++)
+ for (int i = 0; i < W; i++)
+ data[j][i] = x(j, i);
+ }
+
+ constexpr operator num*() & { return (num*)data; }
+ constexpr operator const num*() const& { return (const num*)data; }
// XXX add more operators as needed, third-party dependencies mostly
// not needed merely for matrix algebra -sh 20141030
- template<int h__ = h_>
- static std::enable_if_t<h_ == w_, Mat<num, h__, h__>> eye()
+ template<int H_ = H>
+ static constexpr std::enable_if_t<H == W, Mat<num, H_, H_>> eye()
{
- static_assert(h_ == h__, "");
+ static_assert(H == H_);
- Mat<num, h_, h_> ret;
- for (int j = 0; j < h_; j++)
- for (int i = 0; i < w_; i++)
+ Mat<num, H, H> ret;
+ for (int j = 0; j < H; j++)
+ for (int i = 0; i < W; i++)
ret.data[j][i] = 0;
- for (int i = 0; i < h_; i++)
+ for (int i = 0; i < H; i++)
ret.data[i][i] = 1;
return ret;
}
- constexpr Mat<num, w_, h_> t() const
+ constexpr Mat<num, W, H> t() const
{
- Mat<num, w_, h_> ret;
+ Mat<num, W, H> ret;
- for (int j = 0; j < h_; j++)
- for (int i = 0; i < w_; i++)
+ for (int j = 0; j < H; j++)
+ for (int i = 0; i < W; i++)
ret(i, j) = data[j][i];
return ret;
}
+
+ constexpr Mat<num, H, W>& operator=(const Mat<num, H, W>& rhs)
+ {
+ for (unsigned j = 0; j < H; j++)
+ for (unsigned i = 0; i < W; i++)
+ data[j][i] = rhs(j, i);
+
+ return *this;
+ }
};
+template<unsigned k, typename num, int h, int w>
+constexpr num get(const Mat<num, h, w>& m) { return m(k); }
+
+template<unsigned k, typename num, int h, int w>
+constexpr num& get(Mat<num, h, w>& m) { return m(k); }
+
+} // ns simple_mat
+
+template<typename num, int h, int w>
+using Mat = simple_mat::Mat<num, h, w>;
+
template<typename num, int h, int w>
constexpr Mat<num, h, w> operator*(num scalar, const Mat<num, h, w>& mat)
{
return mat * scalar;
}
-template<typename num, int h_, int w_>
-constexpr Mat<num, h_, w_> operator*(const Mat<num, h_, w_>& self, num other)
+template<typename num, int H, int W>
+constexpr Mat<num, H, W> operator*(const Mat<num, H, W>& self, num other)
{
- Mat<num, h_, w_> ret;
- for (int j = 0; j < h_; j++)
- for (int i = 0; i < w_; i++)
+ Mat<num, H, W> ret;
+ for (int j = 0; j < H; j++)
+ for (int i = 0; i < W; i++)
ret(j, i) = self(j, i) * other;
return ret;
}
-} // ns simple_mat_detail
+namespace std {
+ template<typename num, int H, int W>
+ struct tuple_size<Mat<num, H, W>> :
+ std::integral_constant<std::size_t, H == 1 || W == 1 ? W * H : 0>
+ {};
-template<typename num, int h, int w>
-using Mat = simple_mat_detail::Mat<num, h, w>;
+ template<std::size_t k, typename num, int h, int w>
+ struct tuple_element<k, Mat<num, h, w>>
+ {
+ using type = std::remove_const_t<std::remove_reference_t<num>>;
+ };
+} // ns std
diff --git a/compat/sleep.cpp b/compat/sleep.cpp
new file mode 100644
index 00000000..e64e6254
--- /dev/null
+++ b/compat/sleep.cpp
@@ -0,0 +1,23 @@
+#include "sleep.hpp"
+
+#ifdef _WIN32
+# include <windows.h>
+#else
+# include <unistd.h>
+#endif
+
+namespace portable
+{
+ void sleep(int milliseconds)
+ {
+ if (milliseconds > 0)
+ {
+#ifdef _WIN32
+
+ Sleep((unsigned)milliseconds);
+#else
+ usleep(unsigned(milliseconds) * 1000U); // takes microseconds
+#endif
+ }
+ }
+}
diff --git a/compat/sleep.hpp b/compat/sleep.hpp
index 4cd7a397..9419b36b 100644
--- a/compat/sleep.hpp
+++ b/compat/sleep.hpp
@@ -1,24 +1,7 @@
#pragma once
-#ifdef _WIN32
-# include <windows.h>
-#else
-# include <unistd.h>
-#endif
+#include "export.hpp"
-namespace portable
-{
-#ifdef _WIN32
- inline void sleep(int milliseconds)
- {
- if (milliseconds > 0)
- Sleep(milliseconds);
- }
-#else
- inline void sleep(int milliseconds)
- {
- if (milliseconds > 0)
- usleep(unsigned(milliseconds) * 1000U); // takes microseconds
- }
-#endif
+namespace portable {
+ OTR_COMPAT_EXPORT void sleep(int milliseconds);
}
diff --git a/compat/sysexits.hpp b/compat/sysexits.hpp
new file mode 100644
index 00000000..6747fc88
--- /dev/null
+++ b/compat/sysexits.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <cstdlib> // for EXIT_SUCCESS, EXIT_FAILRUE
+
+#ifndef _WIN32
+# include <sysexits.h>
+#else
+// this conforms to BSD sysexits(3)
+// reference the manual page on FreeBSD or Linux for semantics
+# define EX_OK 0
+# define EX_USAGE 64
+# define EX_DATAERR 65
+# define EX_NOINPUT 66
+# define EX_NOUSER 67
+# define EX_NOHOST 68
+# define EX_UNAVAILABLE 69
+# define EX_SOFTWARE 70
+# define EX_OSERR 71
+# define EX_OSFILE 72
+# define EX_CANTCREAT 73
+# define EX_IOERR 74
+# define EX_TEMPFAIL 75
+# define EX_PROTOCOL 76
+# define EX_NOPERM 77
+# define EX_CONFIG 78
+#endif
+
+
diff --git a/compat/thread-name.cpp b/compat/thread-name.cpp
new file mode 100644
index 00000000..08a7d628
--- /dev/null
+++ b/compat/thread-name.cpp
@@ -0,0 +1,87 @@
+#include "thread-name.hpp"
+#ifdef _WIN32
+# include <QDebug>
+# include <windows.h>
+#else
+# include <QThread>
+#endif
+
+namespace portable {
+
+#ifdef _WIN32
+
+#ifdef _MSC_VER
+struct THREADNAME_INFO
+{
+ DWORD dwType; // must be 0x1000
+ LPCSTR szName; // pointer to name (in user addr space)
+ HANDLE dwThreadID; // thread ID (-1=caller thread)
+ DWORD dwFlags; // reserved for future use, must be zero
+};
+
+static inline
+void set_curthread_name_old_(const char* name)
+{
+ HANDLE curthread = GetCurrentThread();
+ THREADNAME_INFO info; // NOLINT(cppcoreguidelines-pro-type-member-init)
+ info.dwType = 0x1000;
+ info.szName = name;
+ info.dwThreadID = curthread;
+ info.dwFlags = 0;
+ __try
+ {
+ static_assert(sizeof(info) % sizeof(unsigned) == 0);
+ unsigned sz = sizeof(info)/sizeof(unsigned);
+ RaiseException(0x406D1388, 0, sz, (const ULONG_PTR*)&info);
+ }
+ __except (EXCEPTION_CONTINUE_EXECUTION)
+ {
+ }
+}
+
+static inline
+void set_curthread_name_old(const QString& name_)
+{
+ QByteArray str = name_.toLocal8Bit();
+ const char* name = str.constData();
+
+ set_curthread_name_old_(name);
+}
+#else
+
+static inline void set_curthread_name_old(const QString&) {}
+
+#endif
+
+using SetThreadDescr_type = HRESULT (__stdcall *)(HANDLE, const wchar_t*);
+
+static SetThreadDescr_type get_funptr()
+{
+ HMODULE module;
+ if (GetModuleHandleExA(0, "kernel32.dll", &module))
+ return (SetThreadDescr_type)GetProcAddress(module, "SetThreadDescription");
+ else
+ return nullptr;
+}
+
+void set_curthread_name(const QString& name)
+{
+ static_assert(sizeof(wchar_t) == sizeof(decltype(*QString().utf16())));
+ static const SetThreadDescr_type fn = get_funptr();
+
+ if (fn)
+ fn(GetCurrentThread(), (const wchar_t*)name.utf16());
+ else
+ set_curthread_name_old(name);
+}
+
+#else
+
+void set_curthread_name(const QString& name)
+{
+ QThread::currentThread()->setObjectName(name);
+}
+
+#endif
+
+} // ns portable
diff --git a/compat/thread-name.hpp b/compat/thread-name.hpp
new file mode 100644
index 00000000..21256003
--- /dev/null
+++ b/compat/thread-name.hpp
@@ -0,0 +1,6 @@
+#pragma once
+#include "export.hpp"
+#include <QString>
+namespace portable {
+ OTR_COMPAT_EXPORT void set_curthread_name(const QString& name);
+}
diff --git a/compat/time.hpp b/compat/time.hpp
index d34e4e8c..0d171038 100644
--- a/compat/time.hpp
+++ b/compat/time.hpp
@@ -7,17 +7,9 @@ namespace time_units {
template<typename repr, typename ratio = std::ratio<1>>
using duration = std::chrono::duration<repr, ratio>;
-template<typename t, typename u>
-static inline constexpr auto time_cast(const u& in)
-{
- return std::chrono::duration_cast<t>(in);
-}
-
-using secs = duration<double>;
-using secs_ = duration<long>;
-using ms = duration<double, std::milli>;
-using us = duration<double, std::micro>;
-using us_ = duration<long long, std::micro>;
-using ns = duration<long long, std::nano>;
+using secs = duration<float>;
+using ms = duration<float, std::milli>;
+using us = duration<float, std::micro>;
+using ns = duration<float, std::nano>;
} // ns time_units
diff --git a/compat/timer.cpp b/compat/timer.cpp
index 162ba16f..fdea185a 100644
--- a/compat/timer.cpp
+++ b/compat/timer.cpp
@@ -6,10 +6,12 @@
* notice appear in all copies.
*/
-#include <cassert>
+#undef NDEBUG
#include "timer.hpp"
+#include <cassert>
#include <cmath>
+#include <QDebug>
Timer::Timer()
{
@@ -21,40 +23,25 @@ void Timer::start()
gettime(&state);
}
-// nanoseconds
-
-long long Timer::elapsed_nsecs() const
-{
- timespec cur{};
- gettime(&cur);
- return conv_nsecs(cur);
-}
-
-long long Timer::conv_nsecs(const struct timespec& cur) const
+struct timespec Timer::get_delta() const
{
- return (cur.tv_sec - state.tv_sec) * 1000000000LL + (cur.tv_nsec - state.tv_nsec);
-}
-
-// microseconds
-
-double Timer::elapsed_usecs() const
-{
- timespec cur{};
- gettime(&cur);
- const long long nsecs = conv_nsecs(cur);
- return nsecs * 1e-3;
+ struct timespec ts; // NOLINT
+ gettime(&ts);
+ return { ts.tv_sec - state.tv_sec, ts.tv_nsec - state.tv_nsec };
}
// milliseconds
double Timer::elapsed_ms() const
{
- return elapsed_usecs() / 1000.;
+ struct timespec delta = get_delta();
+ return delta.tv_sec * 1000 + delta.tv_nsec * 1e-6;
}
double Timer::elapsed_seconds() const
{
- return double(elapsed_nsecs() * 1e-9L);
+ struct timespec delta = get_delta();
+ return delta.tv_sec + delta.tv_nsec * 1e-9;
}
// --
@@ -64,29 +51,24 @@ double Timer::elapsed_seconds() const
#if defined (_WIN32)
# include <windows.h>
-static LARGE_INTEGER otr_get_clock_frequency()
+static auto otr_get_clock_frequency()
{
LARGE_INTEGER freq{};
- const BOOL ret = QueryPerformanceFrequency(&freq);
+ BOOL ret = QueryPerformanceFrequency(&freq);
assert(ret && "QueryPerformanceFrequency failed");
- return freq;
+ return freq.QuadPart;
}
-static void otr_clock_gettime(timespec* ts)
+void Timer::gettime(timespec* state)
{
- static const LARGE_INTEGER freq = otr_get_clock_frequency();
-
+ static const auto freq = otr_get_clock_frequency();
LARGE_INTEGER d;
+ BOOL ret = QueryPerformanceCounter(&d);
+ assert(ret && "QueryPerformanceCounter failed");
- (void) QueryPerformanceCounter(&d);
-
- using ll = long long;
- const ll part = ll(std::roundl((d.QuadPart * 1000000000.L) / ll(freq.QuadPart)));
- using t_s = decltype(ts->tv_sec);
- using t_ns = decltype(ts->tv_nsec);
-
- ts->tv_sec = t_s(part / 1000000000);
- ts->tv_nsec = t_ns(part % 1000000000);
+ constexpr int nsec = 1'000'000 * 1000;
+ state->tv_sec = (time_t)(d.QuadPart/freq);
+ state->tv_nsec = (decltype(state->tv_nsec))(d.QuadPart % freq * nsec / freq);
}
#elif defined __MACH__
@@ -96,32 +78,30 @@ static void otr_clock_gettime(timespec* ts)
static mach_timebase_info_data_t otr_get_mach_frequency()
{
mach_timebase_info_data_t timebase_info;
- (void) mach_timebase_info(&timebase_info);
+ kern_return_t status = mach_timebase_info(&timebase_info);
+ assert(status == KERN_SUCCESS && "mach_timebase_info failed");
return timebase_info;
}
-static void otr_clock_gettime(timespec* ts)
+void Timer::gettime(timespec* ts)
{
static const mach_timebase_info_data_t timebase_info = otr_get_mach_frequency();
uint64_t state, nsec;
state = mach_absolute_time();
nsec = state * timebase_info.numer / timebase_info.denom;
- ts->tv_sec = nsec / 1000000000L;
- ts->tv_nsec = nsec % 1000000000L;
+ ts->tv_sec = nsec / 1000000000UL;
+ ts->tv_nsec = nsec % 1000000000UL;
+}
+
+#else
+
+void Timer::gettime(timespec* ts)
+{
+ int error = clock_gettime(CLOCK_MONOTONIC, ts);
+ assert(error == 0 && "clock_gettime failed");
}
#endif
// common
-void Timer::gettime(timespec* state)
-{
-#if defined(_WIN32) || defined(__MACH__)
- otr_clock_gettime(state);
-#elif defined CLOCK_MONOTONIC
- const int res = clock_gettime(CLOCK_MONOTONIC, state);
- assert(res == 0 && "must support CLOCK_MONOTONIC");
-#else
-# error "timer query method not known"
-#endif
-}
diff --git a/compat/timer.hpp b/compat/timer.hpp
index 03b537ac..47072226 100644
--- a/compat/timer.hpp
+++ b/compat/timer.hpp
@@ -10,43 +10,19 @@
#include "export.hpp"
#include "time.hpp"
-
#include <ctime>
-class OTR_COMPAT_EXPORT Timer final
+struct OTR_COMPAT_EXPORT Timer final
{
- struct timespec state;
- long long conv_nsecs(const struct timespec& cur) const;
-
- static void gettime(struct timespec* state);
-
- using ns = time_units::ns;
-public:
Timer();
void start();
- template<typename t>
- t elapsed() const
- {
- using namespace time_units;
- return time_cast<t>(ns(elapsed_nsecs()));
- }
-
- template<typename t>
- bool is_elapsed(const t& time_value)
- {
- using namespace time_units;
-
- if (unlikely(elapsed<ns>() >= time_value))
- {
- start();
- return true;
- }
- return false;
- }
-
- long long elapsed_nsecs() const;
- double elapsed_usecs() const;
double elapsed_ms() const;
double elapsed_seconds() const;
+
+private:
+ struct timespec state {};
+ static void gettime(struct timespec* state);
+ struct timespec get_delta() const;
+ using ns = time_units::ns;
};
diff --git a/compat/tr.hpp b/compat/tr.hpp
new file mode 100644
index 00000000..c5a561e7
--- /dev/null
+++ b/compat/tr.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <QObject>
+
+// The class does nothing except provide a fake assignment operator for QObject
+// It's meant to be used inside classes that need i18n support but are returned by value.
+
+struct TR : QObject
+{
+ TR() = default;
+ TR(const TR&) : QObject(nullptr) {}
+
+ TR& operator=(const TR&) { return *this; }
+};
diff --git a/compat/variance.hpp b/compat/variance.hpp
index 7a83154c..8abefd33 100644
--- a/compat/variance.hpp
+++ b/compat/variance.hpp
@@ -1,7 +1,6 @@
#pragma once
#include <cmath>
-#include <cinttypes>
// no copyright information other than the attribution below.
@@ -18,19 +17,18 @@
// Comparison of Several Algorithms for Computing Sample Means and Variances.
// Journal of the American Statistical Association, Vol. 69, No. 348, 859-866.
-class variance
+struct variance final
{
- double m_old, m_new, s_old, s_new;
- std::uintptr_t cnt;
-
-public:
- using size_type = std::uintptr_t;
+ using size_type = unsigned;
- variance() : cnt(0) {}
+ variance& operator=(variance const&) = default;
+ void clear() { *this = {}; }
- void clear() { *this = variance(); }
+ constexpr size_type count() const { return cnt; }
- size_type count() { return cnt; }
+ constexpr double avg() const { return cnt > 0 ? m_new : 0; }
+ constexpr double Var() const { return cnt > 1 ? s_new/(cnt - 1) : 0; }
+ double stddev() const { return std::sqrt(Var()); }
void input(double x)
{
@@ -51,7 +49,7 @@ public:
}
}
- double avg() const { return cnt > 0 ? m_new : 0; }
- double Var() const { return cnt > 1 ? s_new/(cnt - 1) : 0; }
- double stddev() const { return std::sqrt(Var()); }
+private:
+ double m_old, m_new, s_old, s_new;
+ size_type cnt = 0;
};
diff --git a/contrib-noinst/important-stuff/game-data.exe b/contrib-noinst/important-stuff/game-data.exe
new file mode 100644
index 00000000..4e43a540
--- /dev/null
+++ b/contrib-noinst/important-stuff/game-data.exe
Binary files differ
diff --git a/contrib/freepie-udp/FreePIE_IMU_Android9.apk b/contrib/freepie-udp/FreePIE_IMU_Android9.apk
new file mode 100644
index 00000000..cab42006
--- /dev/null
+++ b/contrib/freepie-udp/FreePIE_IMU_Android9.apk
Binary files differ
diff --git a/contrib/freepie-udp/com.freepie.android.imu.apk b/contrib/freepie-udp/com.freepie.android.imu.apk
deleted file mode 100644
index b1f052aa..00000000
--- a/contrib/freepie-udp/com.freepie.android.imu.apk
+++ /dev/null
Binary files differ
diff --git a/contrib/freepie-udp/com.wishsalad.wishimu-debug.apk b/contrib/freepie-udp/com.wishsalad.wishimu-debug.apk
new file mode 100644
index 00000000..a09bd0a3
--- /dev/null
+++ b/contrib/freepie-udp/com.wishsalad.wishimu-debug.apk
Binary files differ
diff --git a/contrib/freepie-udp/src-WishIMU.zip b/contrib/freepie-udp/src-WishIMU.zip
new file mode 100644
index 00000000..f60ce3b7
--- /dev/null
+++ b/contrib/freepie-udp/src-WishIMU.zip
Binary files differ
diff --git a/contrib/libusbK-dev-kit/libusbK-3.1.0.0-setup.exe b/contrib/libusbK-dev-kit/libusbK-3.1.0.0-setup.exe
new file mode 100644
index 00000000..17d13b16
--- /dev/null
+++ b/contrib/libusbK-dev-kit/libusbK-3.1.0.0-setup.exe
Binary files differ
diff --git a/contrib/libusbK-inf-wizard/libusbK-inf-wizard.exe b/contrib/libusbK-inf-wizard/libusbK-inf-wizard.exe
new file mode 100644
index 00000000..47c9f175
--- /dev/null
+++ b/contrib/libusbK-inf-wizard/libusbK-inf-wizard.exe
Binary files differ
diff --git a/contrib/libusbK-inf-wizard/license/AUTHORS-libusb0.txt b/contrib/libusbK-inf-wizard/license/AUTHORS-libusb0.txt
new file mode 100644
index 00000000..3ad12f72
--- /dev/null
+++ b/contrib/libusbK-inf-wizard/license/AUTHORS-libusb0.txt
@@ -0,0 +1,20 @@
+This software is distributed under the following licenses:
+Driver: GNU General Public License (GPL)
+Library: GNU Lesser General Public (LGPL)
+Test Files: GNU Lesser General Public (LGPL)
+Filter Installer: GNU Lesser General Public (LGPL)
+
+Library, Test Programs:
+
+Stephan Meyer, <ste_meyer@web.de>
+Johannes Erdfelt, <johannes@erdfelt.com>
+Thomas Sailer, <sailer@ife.ee.ethz.ch>
+
+Drivers, Installer:
+
+Stephan Meyer, <ste_meyer@web.de>
+Travis Robinson, <libusbdotnet@gmail.com>
+
+Testing, Technical support:
+
+Xiaofan Chen, <xiaofanc@gmail.com>
diff --git a/contrib/libusbK-inf-wizard/license/AUTHORS-libusbK.txt b/contrib/libusbK-inf-wizard/license/AUTHORS-libusbK.txt
new file mode 100644
index 00000000..99f27a45
--- /dev/null
+++ b/contrib/libusbK-inf-wizard/license/AUTHORS-libusbK.txt
@@ -0,0 +1,40 @@
+CONTRIBUTORS
+-----------------------------------------------------------------------
+Development : Travis Lee Robinson (libusbdotnet@gmail.com)
+Testing : Xiaofan Chen (xiaofanc@gmail.com)
+
+LICENSE
+-----------------------------------------------------------------------
+Copyright (c) 2011-2012 Travis Lee Robinson. All rights reserved.
+
+APPLICABLE FOR ALL LIBUSBK BINARIES AND SOURCE CODE UNLESS OTHERWISE
+SPECIFIED. PLEASE SEE INDIVIDUAL COMPONENTS LICENSING TERMS FOR DETAILS.
+
+NOTE: Portions of dpscat use source code from libwdi which is licensed
+for LGPL use only. (See dpscat.c)
+
+NOTE: libusbK-inf-wizard.exe is linked to libwdi which is licensed for
+LGPL use only.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Travis Lee Robinson nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TRAVIS ROBINSON BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/contrib/libusbK-inf-wizard/license/LICENSE-bsd.txt b/contrib/libusbK-inf-wizard/license/LICENSE-bsd.txt
new file mode 100644
index 00000000..b80c667f
--- /dev/null
+++ b/contrib/libusbK-inf-wizard/license/LICENSE-bsd.txt
@@ -0,0 +1,33 @@
+Copyright (c) 2011-2012 Travis Lee Robinson. All rights reserved.
+
+APPLICABLE FOR ALL LIBUSBK BINARIES AND SOURCE CODE UNLESS OTHERWISE
+SPECIFIED. PLEASE SEE INDIVIDUAL COMPONENTS LICENSING TERMS FOR DETAILS.
+
+NOTE: Portions of dpscat use source code from libwdi which is licensed
+for LGPL use only. (See dpscat.c)
+
+NOTE: libusbK-inf-wizard.exe is linked to libwdi which is licensed for
+LGPL use only.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Travis Lee Robinson nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TRAVIS ROBINSON BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/contrib/libusbK-inf-wizard/license/LICENSE-gpl3.txt b/contrib/libusbK-inf-wizard/license/LICENSE-gpl3.txt
new file mode 100644
index 00000000..eff3f9ac
--- /dev/null
+++ b/contrib/libusbK-inf-wizard/license/LICENSE-gpl3.txt
@@ -0,0 +1,686 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TRAVIS ROBINSON BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/contrib/libusbK-inf-wizard/license/LICENSE-lgpl3.txt b/contrib/libusbK-inf-wizard/license/LICENSE-lgpl3.txt
new file mode 100644
index 00000000..65c5ca88
--- /dev/null
+++ b/contrib/libusbK-inf-wizard/license/LICENSE-lgpl3.txt
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/contrib/npclient/npclient.c b/contrib/npclient/npclient.c
index 7b2866f0..03fa589a 100644
--- a/contrib/npclient/npclient.c
+++ b/contrib/npclient/npclient.c
@@ -3,9 +3,9 @@
#include <stdarg.h>
#include <stdlib.h>
#include <stdbool.h>
-#include <stdint.h>
#include <math.h>
#include <string.h>
+#include <stdio.h>
#include <windows.h>
@@ -14,8 +14,6 @@
#define UNUSED(var) (void)var
-//#define DEBUG
-
typedef struct TFreeTrackData
{
int DataID;
@@ -41,80 +39,44 @@ typedef struct TFreeTrackData
float Y3;
float X4;
float Y4;
-} volatile TFreeTrackData;
+} TFreeTrackData;
typedef struct FTMemMap
{
TFreeTrackData data;
- uint32_t GameId;
+ __int32 GameId;
unsigned char table[8];
- uint32_t GameId2;
-} volatile FTMemMap;
+ __int32 GameId2;
+} FTMemMap;
+
+static bool bEncryptionChecked = false;
+static double r = 0, p = 0, y = 0, tx = 0, ty = 0, tz = 0;
#define NP_DECLSPEC __declspec(dllexport)
#define NP_EXPORT(t) t NP_DECLSPEC __stdcall
#define NP_AXIS_MAX 16383
-static HANDLE hFTMemMap = 0;
-static FTMemMap* pMemData = 0;
-
-#if defined _MSC_VER
-# define force_inline __forceinline
-#else
-# define force_inline __attribute__((always_inline, gnu_inline)) inline
-#endif
+static bool FTCreateMapping(void);
+static void FTDestroyMapping(void);
+static __inline double clamp(double x, double xmin, double xmax);
+static __inline double clamp_(double x);
-#ifdef DEBUG
-# include <stdio.h>
-# define dbg_report(...) do { if (debug_stream) { fprintf(debug_stream, __VA_ARGS__); fprintf(debug_stream, "\n"); fflush(debug_stream); } } while (0)
+#if DEBUG
static FILE *debug_stream;
+#define dbg_report(...) do { if (debug_stream) { fprintf(debug_stream, __VA_ARGS__); fflush(debug_stream); } } while(0);
#else
-# define dbg_report(...) do { (void)0; } while (0)
+#define dbg_report(...)
#endif
-static bool FTCreateMapping(void)
-{
- if (pMemData)
- return true;
-
- dbg_report("FTCreateMapping request (pMemData == NULL)");
-
- HANDLE hFTMutex = CreateMutexA(NULL, FALSE, FREETRACK_MUTEX);
- CloseHandle(hFTMutex);
-
- hFTMemMap = CreateFileMappingA(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, sizeof(FTMemMap), FT_MM_DATA);
- pMemData = (FTMemMap *) MapViewOfFile(hFTMemMap, FILE_MAP_WRITE, 0, 0, sizeof(FTMemMap));
- return pMemData != NULL;
-}
-
-static void FTDestroyMapping(void)
-{
- if (pMemData != NULL)
- {
- InterlockedExchange((LONG volatile*) &pMemData->data.DataID, -1);
- UnmapViewOfFile((void*)pMemData);
- }
-
- CloseHandle(hFTMemMap);
- pMemData = 0;
- hFTMemMap = 0;
-}
-
-static force_inline double clamp(double x, double xmin, double xmax)
-{
- if (x > xmax)
- return xmax;
-
- if (x < xmin)
- return xmin;
-
- return x;
-}
+typedef enum npclient_status_ {
+ NPCLIENT_STATUS_OK,
+ NPCLIENT_STATUS_DISABLED
+} npclient_status;
-static force_inline double clamp_(double x)
-{
- return clamp(x, -NP_AXIS_MAX, NP_AXIS_MAX);
-}
+static HANDLE hFTMemMap = 0;
+static FTMemMap volatile * pMemData = 0;
+static bool bEncryption = false;
+static unsigned char table[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
typedef struct tir_data
{
@@ -124,13 +86,13 @@ typedef struct tir_data
float roll, pitch, yaw;
float tx, ty, tz;
float padding[9];
-} tir_data;
+} tir_data_t;
typedef struct tir_signature
{
char DllSignature[200];
char AppSignature[200];
-} tir_signature;
+} tir_signature_t;
BOOL DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
@@ -139,17 +101,16 @@ BOOL DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
-#ifdef DEBUG
+#if DEBUG
debug_stream = fopen("c:\\NPClient.log", "a");
#endif
-
#ifdef _WIN64
- dbg_report("\n= WIN64 =========================================================================================");
+ dbg_report("\n= WIN64 =========================================================================================\n");
#else
- dbg_report("\n= WIN32 =========================================================================================");
+ dbg_report("\n= WIN32 =========================================================================================\n");
#endif
- dbg_report("DllMain: (%p, %ld, %p)", (void*) hinstDLL, (long) fdwReason, lpvReserved);
- dbg_report("DllMain: Attach request");
+ dbg_report("DllMain: (%p, %ld, %p)\n", (void*) hinstDLL, (long) fdwReason, lpvReserved);
+ dbg_report("DllMain: Attach request\n");
DisableThreadLibraryCalls(hinstDLL);
#if 0
timeBeginPeriod(1);
@@ -157,13 +118,12 @@ BOOL DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
break;
case DLL_PROCESS_DETACH:
- dbg_report("DllMain: Detach");
+ dbg_report("DllMain: Detach\n");
dbg_report("DllMain: (%p, %ld, %p)\n", (void*) hinstDLL, (long) fdwReason, lpvReserved);
- dbg_report("==========================================================================================");
+ dbg_report("==========================================================================================\n");
#if 0
timeEndPeriod(1);
#endif
-
FTDestroyMapping();
break;
}
@@ -175,7 +135,7 @@ BOOL DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
NP_EXPORT(int) NPPriv_ClientNotify(void)
{
- dbg_report("stub");
+ dbg_report("stub\n");
/* @stub in .spec */
return 0;
}
@@ -185,7 +145,7 @@ NP_EXPORT(int) NPPriv_ClientNotify(void)
NP_EXPORT(int) NPPriv_GetLastError(void)
{
- dbg_report("stub");
+ dbg_report("stub\n");
/* @stub in .spec */
return 0;
}
@@ -195,7 +155,7 @@ NP_EXPORT(int) NPPriv_GetLastError(void)
NP_EXPORT(int) NPPriv_SetData(void)
{
- dbg_report("stub");
+ dbg_report("stub\n");
/* @stub in .spec */
return 0;
}
@@ -205,7 +165,7 @@ NP_EXPORT(int) NPPriv_SetData(void)
NP_EXPORT(int) NPPriv_SetLastError(void)
{
- dbg_report("stub");
+ dbg_report("stub\n");
/* @stub in .spec */
return 0;
}
@@ -215,7 +175,7 @@ NP_EXPORT(int) NPPriv_SetLastError(void)
NP_EXPORT(int) NPPriv_SetParameter(void)
{
- dbg_report("stub");
+ dbg_report("stub\n");
/* @stub in .spec */
return 0;
}
@@ -225,7 +185,7 @@ NP_EXPORT(int) NPPriv_SetParameter(void)
NP_EXPORT(int) NPPriv_SetSignature(void)
{
- dbg_report("stub");
+ dbg_report("stub\n");
/* @stub in .spec */
return 0;
}
@@ -235,7 +195,7 @@ NP_EXPORT(int) NPPriv_SetSignature(void)
NP_EXPORT(int) NPPriv_SetVersion(void)
{
- dbg_report("stub");
+ dbg_report("stub\n");
/* @stub in .spec */
return 0;
}
@@ -302,7 +262,7 @@ static unsigned cksum(unsigned char buf[], unsigned size)
return (unsigned)c;
}
-static inline void enhance(unsigned char buf[], unsigned size, unsigned char table[], unsigned table_size)
+static __inline void enhance(unsigned char buf[], unsigned size, unsigned char table[], unsigned table_size)
{
unsigned table_ptr = 0;
unsigned char var = 0x88;
@@ -328,28 +288,24 @@ static inline void enhance(unsigned char buf[], unsigned size, unsigned char tab
* NP_GetData (NPCLIENT.8)
*/
-typedef enum npclient_status_ {
- NPCLIENT_STATUS_OK,
- NPCLIENT_STATUS_DISABLED
-} npclient_status;
-
-NP_EXPORT(int) NP_GetData(tir_data* data)
+NP_EXPORT(int) NP_GetData(tir_data_t * data)
{
- static double r = 0, p = 0, y = 0, tx = 0, ty = 0, tz = 0;
- static unsigned frameno = 0;
- static unsigned char table[8] = {0};
- static bool bEncryption = false;
- static bool bEncryptionChecked = false;
- int i, data_id = -1;
-
+ static int frameno = 0;
+ int i;
+#if DEBUG
+ int recv = 0;
+#endif
if (!FTCreateMapping())
{
- dbg_report("Can't open mapping");
- return NPCLIENT_STATUS_DISABLED;
+ dbg_report("Can't open mapping\n");
+ return 0;
}
- if (pMemData->GameId > 0 && pMemData->GameId == pMemData->GameId2)
+ if (pMemData)
{
+#if DEBUG
+ recv = 1;
+#endif
y = pMemData->data.Yaw * NP_AXIS_MAX / M_PI;
p = pMemData->data.Pitch * NP_AXIS_MAX / M_PI;
r = pMemData->data.Roll * NP_AXIS_MAX / M_PI;
@@ -358,9 +314,9 @@ NP_EXPORT(int) NP_GetData(tir_data* data)
ty = pMemData->data.Y * NP_AXIS_MAX / 500.;
tz = pMemData->data.Z * NP_AXIS_MAX / 500.;
- if (!bEncryptionChecked)
+ if (pMemData->GameId == pMemData->GameId2 && !bEncryptionChecked)
{
- dbg_report("NP_GetData: game = %d", pMemData->GameId);
+ dbg_report("NP_GetData: game = %d\n", pMemData->GameId);
bEncryptionChecked = true;
memcpy(table, (void*)pMemData->table, 8);
for (i = 0; i < 8; i++)
@@ -369,29 +325,17 @@ NP_EXPORT(int) NP_GetData(tir_data* data)
bEncryption = true;
break;
}
- dbg_report("NP_GetData: Table = %02d %02d %02d %02d %02d %02d %02d %02d", table[0],table[1],table[2],table[3],table[4],table[5], table[6], table[7]);
+ dbg_report("NP_GetData: Table = %02d %02d %02d %02d %02d %02d %02d %02d\n", table[0],table[1],table[2],table[3],table[4],table[5], table[6], table[7]);
}
-
- data_id = InterlockedCompareExchange((LONG volatile*) &pMemData->data.DataID, -1, -1);
}
- data->frame = ++frameno;
+ frameno++;
+ data->frame = frameno;
- bool running = false;
+ bool running = y != 0 || p != 0 || r != 0 ||
+ tx != 0 || ty != 0 || tz != 0;
- if (data_id == 0)
- {
- running = true;
- y = 0, r = 0, p = 0, tx = 0, ty = 0, tz = 0;
- (void)InterlockedCompareExchange((LONG volatile*) &pMemData->data.DataID, -1, 0);
- }
- else if (data_id > 0)
- {
- running = true;
- (void)InterlockedCompareExchange((LONG volatile*) &pMemData->data.DataID, data_id - 1, data_id);
- }
-
- data->status = NPCLIENT_STATUS_OK;
+ data->status = running ? NPCLIENT_STATUS_OK : NPCLIENT_STATUS_DISABLED;
data->cksum = 0;
data->roll = clamp_(r);
@@ -402,18 +346,17 @@ NP_EXPORT(int) NP_GetData(tir_data* data)
data->ty = clamp_(ty);
data->tz = clamp_(tz);
- for (i = 0; i < 9; ++i)
- data->padding[i] = 0;
+ for(i = 0; i < 9; ++i)
+ data->padding[i] = 0.0;
-#ifdef DEBUG
- dbg_report("GetData: rotation: %f %f %f", data->yaw, data->pitch, data->roll);
- dbg_report("GetData: status:%d dataid:%d enc:%d id1:%d id2:%d\n", (int) running, data_id, (int)bEncryption, (int)pMemData->GameId, (int)pMemData->GameId2);
+#if DEBUG
+ dbg_report("GetData: rotation: %d %f %f %f\n", recv, data->yaw, data->pitch, data->roll);
#endif
- data->cksum = cksum((unsigned char*)data, sizeof(*data));
+ data->cksum = cksum((unsigned char*)data, sizeof(tir_data_t));
if (bEncryption)
- enhance((unsigned char*)data, sizeof(*data), table, sizeof(table));
+ enhance((unsigned char*)data, sizeof(tir_data_t), table, sizeof(table));
return running ? NPCLIENT_STATUS_OK : NPCLIENT_STATUS_DISABLED;
}
@@ -424,8 +367,8 @@ NP_EXPORT(int) NP_GetData(tir_data* data)
NP_EXPORT(int) NP_GetParameter(int arg0, int arg1)
{
UNUSED(arg0); UNUSED(arg1);
- dbg_report("GetParameter request: %d %d", arg0, arg1);
- return 0;
+ dbg_report("GetParameter request: %d %d\n", arg0, arg1);
+ return (int) 0;
}
/******************************************************************
@@ -434,7 +377,7 @@ NP_EXPORT(int) NP_GetParameter(int arg0, int arg1)
*
*/
-static unsigned char volatile const part2_2[] = {
+static volatile unsigned char part2_2[200] = {
0xe3, 0xe5, 0x8e, 0xe8, 0x06, 0xd4, 0xab,
0xcf, 0xfa, 0x51, 0xa6, 0x84, 0x69, 0x52,
0x21, 0xde, 0x6b, 0x71, 0xe6, 0xac, 0xaa,
@@ -449,10 +392,11 @@ static unsigned char volatile const part2_2[] = {
0xe4, 0xc0, 0xf1, 0x7f, 0x87, 0xd0, 0x70,
0xa4, 0x04, 0x07, 0x05, 0x69, 0x2a, 0x16,
0x15, 0x55, 0x85, 0xa6, 0x30, 0xc8, 0xb6,
+ 0x00
};
-static unsigned char volatile const part1_2[] = {
+static volatile unsigned char part1_2[200] = {
0x6d, 0x0b, 0xab, 0x56, 0x74, 0xe6, 0x1c,
0xff, 0x24, 0xe8, 0x34, 0x8f, 0x00, 0x63,
0xed, 0x47, 0x5d, 0x9b, 0xe1, 0xe0, 0x1d,
@@ -468,10 +412,10 @@ static unsigned char volatile const part1_2[] = {
0x5d, 0x1a, 0xb4, 0x84, 0x9c, 0x29, 0xf0,
0xe6, 0x69, 0x73, 0x66, 0x0e, 0x4b, 0x3c,
0x7d, 0x99, 0x8b, 0x4e, 0x7d, 0xaf, 0x86,
- 0x92
+ 0x92, 0xff
};
-static unsigned char volatile const part2_1[] = {
+static volatile unsigned char part2_1[200] = {
0x8b, 0x84, 0xfc, 0x8c, 0x71, 0xb5, 0xd9,
0xaa, 0xda, 0x32, 0xc7, 0xe9, 0x0c, 0x20,
0x40, 0xd4, 0x4b, 0x02, 0x89, 0xca, 0xde,
@@ -486,9 +430,10 @@ static unsigned char volatile const part2_1[] = {
0x81, 0x83, 0x9e, 0x11, 0xf3, 0xa2, 0x1f,
0xc8, 0x24, 0x53, 0x60, 0x0a, 0x42, 0x78,
0x7a, 0x39, 0xea, 0xc1, 0x59, 0xad, 0xc5,
+ 0x00
};
-static unsigned char volatile const part1_1[] = {
+static volatile unsigned char part1_1[200] = {
0x1d, 0x79, 0xce, 0x35, 0x1d, 0x95, 0x79,
0xdf, 0x4c, 0x8d, 0x55, 0xeb, 0x20, 0x17,
0x9f, 0x26, 0x3e, 0xf0, 0x88, 0x8e, 0x7a,
@@ -504,30 +449,24 @@ static unsigned char volatile const part1_1[] = {
0x24, 0x7f, 0xf7, 0xeb, 0xf2, 0x5d, 0x82,
0x89, 0x05, 0x53, 0x32, 0x6b, 0x28, 0x54,
0x13, 0xf6, 0xe7, 0x21, 0x1a, 0xc6, 0xe3,
- 0xe1
+ 0xe1, 0xff
};
-NP_EXPORT(int) NP_GetSignature(tir_signature* sig)
+NP_EXPORT(int) NP_GetSignature(tir_signature_t * sig)
{
- unsigned i;
- dbg_report("GetSignature request");
+ int i;
+ dbg_report("GetSignature request\n");
- for (i = 0; i < sizeof(part1_1); i++)
+ for (i = 0; i < 200; i++)
sig->DllSignature[i] = part1_2[i] ^ part1_1[i];
- for (; i < 200; i++)
- sig->DllSignature[i] = '\0';
-
- for (i = 0; i < sizeof(part2_1); i++)
+ for (i = 0; i < 200; i++)
sig->AppSignature[i] = part2_1[i] ^ part2_2[i];
- for (; i < 200; i++)
- sig->AppSignature[i] = '\0';
-
return 0;
}
-NP_EXPORT(int) NP_QueryVersion(unsigned short* version)
+NP_EXPORT(int) NP_QueryVersion(unsigned short * version)
{
- dbg_report("QueryVersion request");
+ dbg_report("QueryVersion request\n");
*version=0x0500;
return 0;
}
@@ -560,7 +499,7 @@ NP_EXPORT(int) NP_RegisterWindowHandle(HWND hwnd)
{
UNUSED(hwnd);
dbg_report("RegisterWindowHandle request: %p\n", (void*) hwnd);
- return 0;
+ return (int) 0;
}
/******************************************************************
* NP_RequestData (NPCLIENT.15)
@@ -570,7 +509,7 @@ NP_EXPORT(int) NP_RequestData(unsigned short req)
{
UNUSED(req);
dbg_report("RequestData request: %d\n", req);
- return 0;
+ return (int) 0;
}
/******************************************************************
* NP_SetParameter (NPCLIENT.16)
@@ -580,7 +519,7 @@ NP_EXPORT(int) NP_SetParameter(int arg0, int arg1)
{
UNUSED(arg0); UNUSED(arg1);
dbg_report("SetParameter request: %d %d\n", arg0, arg1);
- return 0;
+ return (int) 0;
}
/******************************************************************
* NP_StartCursor (NPCLIENT.17)
@@ -589,7 +528,7 @@ NP_EXPORT(int) NP_SetParameter(int arg0, int arg1)
NP_EXPORT(int) NP_StartCursor(void)
{
dbg_report("StartCursor request\n");
- return 0;
+ return (int) 0;
}
/******************************************************************
* NP_StartDataTransmission (NPCLIENT.18)
@@ -599,7 +538,7 @@ NP_EXPORT(int) NP_StartDataTransmission(void)
{
dbg_report("StartDataTransmission request.\n");
- return 0;
+ return (int) 0;
}
/******************************************************************
* NP_StopCursor (NPCLIENT.19)
@@ -608,7 +547,7 @@ NP_EXPORT(int) NP_StartDataTransmission(void)
NP_EXPORT(int) NP_StopCursor(void)
{
dbg_report("StopCursor request\n");
- return 0;
+ return (int) 0;
}
/******************************************************************
* NP_StopDataTransmission (NPCLIENT.20)
@@ -616,7 +555,7 @@ NP_EXPORT(int) NP_StopCursor(void)
NP_EXPORT(int) NP_StopDataTransmission(void)
{
- return 0;
+ return (int) 0;
}
/******************************************************************
* NP_UnregisterWindowHandle (NPCLIENT.21)
@@ -625,6 +564,46 @@ NP_EXPORT(int) NP_StopDataTransmission(void)
NP_EXPORT(int) NP_UnregisterWindowHandle(void)
{
dbg_report("UnregisterWindowHandle request\n");
- return 0;
+ return (int) 0;
}
+static bool FTCreateMapping(void)
+{
+ if (pMemData)
+ return true;
+
+ dbg_report("FTCreateMapping request (pMemData == NULL).\n");
+
+ HANDLE hFTMutex = CreateMutexA(NULL, FALSE, FREETRACK_MUTEX);
+ CloseHandle(hFTMutex);
+
+ hFTMemMap = CreateFileMappingA(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, sizeof(FTMemMap), (LPCSTR) FT_MM_DATA);
+ pMemData = (FTMemMap *) MapViewOfFile(hFTMemMap, FILE_MAP_WRITE, 0, 0, sizeof(FTMemMap));
+ return pMemData != NULL;
+}
+
+static void FTDestroyMapping(void)
+{
+ if (pMemData != NULL)
+ UnmapViewOfFile((void*)pMemData);
+
+ CloseHandle(hFTMemMap);
+ pMemData = 0;
+ hFTMemMap = 0;
+}
+
+static __inline double clamp(double x, double xmin, double xmax)
+{
+ if (x > xmax)
+ return xmax;
+
+ if (x < xmin)
+ return xmin;
+
+ return x;
+}
+
+static __inline double clamp_(double x)
+{
+ return clamp(x, -NP_AXIS_MAX, NP_AXIS_MAX);
+}
diff --git a/coverity/build.sh b/coverity/build.sh
index 9b7ac7f2..d868d64a 100644
--- a/coverity/build.sh
+++ b/coverity/build.sh
@@ -27,7 +27,7 @@ set -e
mydate="$(date --iso-8601=minutes)"
mydir="$(dirname -- "$0")"
-myfile="opentrack-"$mydate".7z"
+myfile="opentrack-$mydate.7z"
cd "$mydir"
for k in opencv aruco libovr-025 libovr-042 libovr-080; do
diff --git a/csv/csv.cpp b/csv/csv.cpp
index 8a5f5784..6b4d0bcd 100644
--- a/csv/csv.cpp
+++ b/csv/csv.cpp
@@ -19,7 +19,7 @@
#include <utility>
#include <algorithm>
-const QTextCodec* const CSV::m_codec = QTextCodec::codecForName("System");
+const QTextCodec* const CSV::m_codec = QTextCodec::codecForName("UTF-8");
const QRegExp CSV::m_rx = QRegExp(QString("((?:(?:[^;\\n]*;?)|(?:\"[^\"]*\";?))*)?\\n?"));
const QRegExp CSV::m_rx2 = QRegExp(QString("(?:\"([^\"]*)\";?)|(?:([^;]*);?)?"));
@@ -111,13 +111,12 @@ bool CSV::getGameData(int id, unsigned char* table, QString& gamename)
CSV csv(&file);
- int lineno = 0;
unsigned tmp[8];
unsigned fuzz[3];
QStringList gameLine;
- while (lineno++, csv.parseLine(gameLine))
+ for (int lineno = 0; csv.parseLine(gameLine); lineno++)
{
//qDebug() << "Column 0: " << gameLine.at(0); // No.
//qDebug() << "Column 1: " << gameLine.at(1); // Game Name
@@ -132,48 +131,37 @@ bool CSV::getGameData(int id, unsigned char* table, QString& gamename)
{
if (gameLine.at(6).compare(id_str, Qt::CaseInsensitive) == 0)
{
- const QString proto(std::move(gameLine.at(3)));
- const QString name(std::move(gameLine.at(1)));
-
- const QByteArray id_cstr = gameLine.at(7).toLatin1();
-
- if (proto == QString("V160"))
- {
- /* nothing */
- }
- else if (id_cstr.length() != 22 ||
- sscanf(id_cstr.constData(),
- "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
- fuzz + 2,
- fuzz + 0,
- tmp + 3,
- tmp + 2,
- tmp + 1,
- tmp + 0,
- tmp + 7,
- tmp + 6,
- tmp + 5,
- tmp + 4,
- fuzz + 1) != 11)
- {
+ const QString& proto = gameLine[3];
+ QString& name = gameLine[1];
+
+ const QByteArray id_cstr = gameLine[7].toLatin1();
+
+ auto do_scanf = [&]() {
+ return sscanf(id_cstr.constData(),
+ "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
+ fuzz + 2,
+ fuzz + 0,
+ tmp + 3, tmp + 2, tmp + 1, tmp + 0,
+ tmp + 7, tmp + 6, tmp + 5, tmp + 4,
+ fuzz + 1);
+ };
+
+ if (proto == QStringLiteral("V160") || id_cstr.length() != 22)
+ (void)0;
+ else if (id_cstr.length() != 22 || do_scanf() != 11)
qDebug() << "scanf failed" << lineno;
- }
else
{
+ using uchar = unsigned char;
for (int i = 0; i < 8; i++)
- {
- using t = unsigned char;
- table[i] = t(tmp[i]);
- }
+ table[i] = uchar(tmp[i]);
}
gamename = std::move(name);
return true;
}
}
else
- {
qDebug() << "malformed csv line" << lineno;
- }
}
if (id)
diff --git a/csv/lang/de_DE.ts b/csv/lang/de_DE.ts
new file mode 100644
index 00000000..1552582e
--- /dev/null
+++ b/csv/lang/de_DE.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+</TS>
diff --git a/csv/lang/zh_CN.ts b/csv/lang/zh_CN.ts
index 6401616d..e5ca8aa9 100644
--- a/csv/lang/zh_CN.ts
+++ b/csv/lang/zh_CN.ts
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
</TS>
diff --git a/cv/CMakeLists.txt b/cv/CMakeLists.txt
index a3f05162..3a8798cc 100644
--- a/cv/CMakeLists.txt
+++ b/cv/CMakeLists.txt
@@ -1,5 +1,15 @@
-find_package(OpenCV 3.0 QUIET)
+include(opentrack-opencv)
+find_package(OpenCV QUIET)
if(OpenCV_FOUND)
+ try_compile(cv_use-ipp "${CMAKE_CURRENT_BINARY_DIR}"
+ SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/ocv-check.cxx"
+ CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${OpenCV_INCLUDE_DIRS}"
+ "-DCXX_STANDARD=20" "-DCXX_STANDARD_REQUIRED=1"
+ OUTPUT_VARIABLE krap)
otr_module(cv STATIC)
- target_include_directories(opentrack-cv SYSTEM PRIVATE ${OpenCV_INCLUDE_DIRS})
+ target_link_libraries(${self} opencv_core opentrack-video)
+ target_include_directories(${self} SYSTEM PRIVATE ${OpenCV_INCLUDE_DIRS})
+ if(cv_use-ipp)
+ target_compile_definitions(${self} PUBLIC -DOTR_HAS_CV_IPP)
+ endif()
endif()
diff --git a/cv/affine.cpp b/cv/affine.cpp
index 1b37305c..66e21aa6 100644
--- a/cv/affine.cpp
+++ b/cv/affine.cpp
@@ -9,23 +9,22 @@
namespace affine_impl {
-Affine::Affine() : R(mat33::eye()), t(0,0,0) {}
-
-Affine::Affine(const mat33& R, const vec3& t) : R(R),t(t) {}
+Affine::Affine() = default;
+Affine::Affine(const mat33& R, const vec3& t) : R(R), t(t) {}
Affine operator*(const Affine& X, const Affine& Y)
{
- return Affine(X.R*Y.R, X.R*Y.t + X.t);
+ return { X.R*Y.R, X.R*Y.t + X.t };
}
Affine operator*(const mat33& X, const Affine& Y)
{
- return Affine(X*Y.R, X*Y.t);
+ return { X*Y.R, X*Y.t };
}
Affine operator*(const Affine& X, const mat33& Y)
{
- return Affine(X.R*Y, X.t);
+ return { X.R*Y, X.t };
}
vec3 operator*(const Affine& X, const vec3& v)
diff --git a/cv/affine.hpp b/cv/affine.hpp
index 3bc85c95..4640e24e 100644
--- a/cv/affine.hpp
+++ b/cv/affine.hpp
@@ -7,12 +7,11 @@
#pragma once
-#include <opencv2/core.hpp>
#include "numeric.hpp"
namespace affine_impl {
-using namespace types;
+using namespace numeric_types;
class Affine final
{
@@ -20,8 +19,8 @@ public:
Affine();
Affine(const mat33& R, const vec3& t);
- mat33 R;
- vec3 t;
+ mat33 R { mat33::eye() };
+ vec3 t { 0, 0, 0 };
};
Affine operator*(const Affine& X, const Affine& Y);
@@ -31,5 +30,5 @@ vec3 operator*(const Affine& X, const vec3& v);
} // ns affine_impl
-using affine_impl::Affine;
-using affine_impl::operator *;
+using Affine = affine_impl::Affine;
+//using affine_impl::operator *;
diff --git a/cv/init.cpp b/cv/init.cpp
new file mode 100644
index 00000000..d883365b
--- /dev/null
+++ b/cv/init.cpp
@@ -0,0 +1,24 @@
+#include "init.hpp"
+#include <type_traits>
+#include <opencv2/core/base.hpp>
+#include <opencv2/core/utility.hpp>
+
+[[noreturn]]
+static
+int error_handler(int, const char* fn, const char* msg, const char* filename, int line, void*)
+{
+ fprintf(stderr, "[%s:%d] opencv: %s at %s\n", filename, line, msg, fn);
+ fflush(stderr);
+ std::abort();
+}
+
+void opencv_init()
+{
+ cv::redirectError(error_handler);
+ cv::setBreakOnError(false);
+ cv::setNumThreads(1);
+#ifdef OTR_HAS_CV_IPP
+ cv::ipp::setUseIPP(true);
+ cv::ipp::setUseIPP_NotExact(true);
+#endif
+}
diff --git a/cv/init.hpp b/cv/init.hpp
new file mode 100644
index 00000000..4c529549
--- /dev/null
+++ b/cv/init.hpp
@@ -0,0 +1,2 @@
+#pragma once
+void opencv_init();
diff --git a/cv/lang/de_DE.ts b/cv/lang/de_DE.ts
new file mode 100644
index 00000000..1552582e
--- /dev/null
+++ b/cv/lang/de_DE.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+</TS>
diff --git a/cv/lang/zh_CN.ts b/cv/lang/zh_CN.ts
index 6401616d..e5ca8aa9 100644
--- a/cv/lang/zh_CN.ts
+++ b/cv/lang/zh_CN.ts
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
</TS>
diff --git a/cv/numeric.hpp b/cv/numeric.hpp
index 0edb7f58..2050e8e4 100644
--- a/cv/numeric.hpp
+++ b/cv/numeric.hpp
@@ -1,13 +1,15 @@
#pragma once
-#include <opencv2/core.hpp>
+#include <type_traits>
+#include <opencv2/core/matx.hpp>
-namespace types {
- using f = double;
+namespace numeric_types {
+ using f = float;
- namespace constants {
- static constexpr inline f eps = f(1e-8);
- }
+ static_assert(std::is_floating_point_v<f>);
+
+ static constexpr f eps = f(1e-8);
+ static constexpr f pi = f(M_PI);
template<int n> using vec = cv::Vec<f, n>;
using vec2 = vec<2>;
diff --git a/cv/ocv-check.cxx b/cv/ocv-check.cxx
new file mode 100644
index 00000000..9efe3610
--- /dev/null
+++ b/cv/ocv-check.cxx
@@ -0,0 +1,7 @@
+#include <opencv2/core.hpp>
+
+void check1()
+{
+ cv::ipp::setUseIPP(true);
+ cv::ipp::setUseIPP_NotExact(true);
+}
diff --git a/cv/translation-calibrator.cpp b/cv/translation-calibrator.cpp
index 1eb9d1fa..6fb3e638 100644
--- a/cv/translation-calibrator.cpp
+++ b/cv/translation-calibrator.cpp
@@ -8,13 +8,15 @@
#include "translation-calibrator.hpp"
#include "compat/euler.hpp"
#include "compat/math.hpp"
+#include "compat/macros.h"
+#include <opencv2/core.hpp>
#include <tuple>
#include <QDebug>
-TranslationCalibrator::TranslationCalibrator(unsigned yaw_rdof, unsigned pitch_rdof, unsigned roll_rdof) :
- yaw_rdof(yaw_rdof), pitch_rdof(pitch_rdof), roll_rdof(roll_rdof)
+TranslationCalibrator::TranslationCalibrator(unsigned yaw_rdof, unsigned pitch_rdof) :
+ yaw_rdof(yaw_rdof), pitch_rdof(pitch_rdof)
{
reset();
}
@@ -22,11 +24,10 @@ TranslationCalibrator::TranslationCalibrator(unsigned yaw_rdof, unsigned pitch_r
void TranslationCalibrator::reset()
{
P = cv::Matx66f::zeros();
- y = cv::Vec6f(0,0,0, 0,0,0);
+ y = { 0,0,0, 0,0,0 };
- used_yaw_poses = vec(1 + iround(360 / yaw_spacing_in_degrees), 0);
- used_pitch_poses = vec(1 + iround(360 / pitch_spacing_in_degrees), 0);
- used_roll_poses = vec(1 + iround(360 / roll_spacing_in_degrees), 0);
+ used_yaw_poses = vec_i(1 + iround(360 / yaw_spacing_in_degrees), 0);
+ used_pitch_poses = vec_i(1 + iround(360 / pitch_spacing_in_degrees), 0);
nsamples = 0;
}
@@ -52,63 +53,70 @@ void TranslationCalibrator::update(const cv::Matx33d& R_CM_k, const cv::Vec3d& t
y += H_k_T * t_CM_k;
}
-std::tuple<cv::Vec3f, cv::Vec3i> TranslationCalibrator::get_estimate()
+using cv_out_vec = TranslationCalibrator::cv_nsample_vec;
+using cv_in_vec = TranslationCalibrator::cv_cal_vec;
+using tt = TranslationCalibrator::tt;
+
+tt TranslationCalibrator::get_estimate()
{
cv::Vec6f x = P.inv() * y;
- unsigned values[3] {};
- vec* in[] { &used_yaw_poses, &used_pitch_poses, &used_roll_poses };
+ vec_i const* in[num_nsample_axis] = { &used_yaw_poses, &used_pitch_poses, };
+ unsigned nsamples[num_cal_axis] {};
- for (unsigned k = 0; k < 3; k++)
+ for (unsigned k = 0; k < num_nsample_axis; k++)
{
- const vec& data = *in[k];
- for (unsigned i = 0; i < data.size(); i++)
- if (data[i])
- values[k]++;
+ vec_i const& data = *in[k];
+ for (unsigned i : data)
+ if (i)
+ nsamples[k]++;
}
qDebug() << "samples total" << nsamples
- << "yaw" << values[0]
- << "pitch" << values[1]
- << "roll" << values[2];
+ << "yaw" << nsamples[0]
+ << "pitch" << nsamples[1];
- return std::make_tuple(cv::Vec3f(-x[0], -x[1], -x[2]),
- cv::Vec3i(values[0], values[1], values[2]));
+ return {
+ { -x[0], -x[1], -x[2] },
+ { nsamples[0], nsamples[1] },
+ };
}
+static constexpr double r2d = 180/M_PI;
+
bool TranslationCalibrator::check_bucket(const cv::Matx33d& R_CM_k)
{
using namespace euler;
- constexpr double r2d = 180/M_PI;
rmat r;
for (unsigned j = 0; j < 3; j++)
for (unsigned i = 0; i < 3; i++)
r(j, i) = R_CM_k(j, i);
- const euler_t ypr = rmat_to_euler(r) * r2d;
+ const Pose_ ypr = rmat_to_euler(r) * r2d;
+
+ auto array_index = [](double val, double spacing) {
+ return (unsigned)iround((val + 180)/spacing);
+ };
- const unsigned yaw_k = iround((ypr(yaw_rdof) + 180)/yaw_spacing_in_degrees);
- const unsigned pitch_k = iround((ypr(pitch_rdof) + 180)/pitch_spacing_in_degrees);
- const unsigned roll_k = iround((ypr(roll_rdof) + 180)/roll_spacing_in_degrees);
+ const unsigned yaw_k = array_index(ypr(yaw_rdof), yaw_spacing_in_degrees);
+ const unsigned pitch_k = array_index(ypr(pitch_rdof), pitch_spacing_in_degrees);
if (yaw_k < used_yaw_poses.size() &&
- pitch_k < used_pitch_poses.size() &&
- roll_k < used_roll_poses.size())
+ pitch_k < used_pitch_poses.size())
{
used_yaw_poses[yaw_k]++;
used_pitch_poses[pitch_k]++;
- used_roll_poses[roll_k]++;
return used_yaw_poses[yaw_k] == 1 ||
- used_pitch_poses[pitch_k] == 1 ||
- used_roll_poses[roll_k] == 1;
+ used_pitch_poses[pitch_k] == 1;
}
else
- qDebug() << "calibrator: index out of range"
- << "yaw" << yaw_k
- << "pitch" << pitch_k
- << "roll" << roll_k;
+ {
+ eval_once(qDebug() << "calibrator: index out of range"
+ << "yaw" << yaw_k
+ << "pitch" << pitch_k);
- return false;
+ return false;
+ }
}
diff --git a/cv/translation-calibrator.hpp b/cv/translation-calibrator.hpp
index c1f6d2ec..3912c938 100644
--- a/cv/translation-calibrator.hpp
+++ b/cv/translation-calibrator.hpp
@@ -7,7 +7,7 @@
#pragma once
-#include <opencv2/core.hpp>
+#include <opencv2/core/matx.hpp>
#include <vector>
//-----------------------------------------------------------------------------
@@ -19,33 +19,38 @@
class TranslationCalibrator
{
-public:
- TranslationCalibrator(unsigned yaw_rdof, unsigned pitch_rdof, unsigned roll_rdof);
+ bool check_bucket(const cv::Matx33d& R_CM_k);
+
+ cv::Matx66f P; // normalized precision matrix = inverse covariance
+ cv::Vec6f y; // P*(-t_MH, t_CH)
+
+ using vec_i = std::vector<unsigned>;
+
+ vec_i used_yaw_poses {};
+ vec_i used_pitch_poses {};
+
+ unsigned yaw_rdof, pitch_rdof, nsamples = 0;
- // reset the calibration process
+public:
+ TranslationCalibrator(unsigned yaw_rdof, unsigned pitch_rdof);
void reset();
// update the current estimate
void update(const cv::Matx33d& R_CM_k, const cv::Vec3d& t_CM_k);
- // get the current estimate for t_MH
- std::tuple<cv::Vec3f, cv::Vec3i> get_estimate();
-
-private:
- bool check_bucket(const cv::Matx33d& R_CM_k);
-
- cv::Matx66f P; // normalized precision matrix = inverse covariance
- cv::Vec6f y; // P*(-t_MH, t_CH)
+ // we're bringing in 3DOF samples but the calibrator only
+ // checks yaw and pitch
- using vec = std::vector<unsigned>;
+ static constexpr unsigned num_cal_axis = 3;
+ static constexpr unsigned num_nsample_axis = 2;
- vec used_yaw_poses;
- vec used_pitch_poses;
- vec used_roll_poses;
+ using cv_cal_vec = cv::Vec<float, num_cal_axis>;
+ using cv_nsample_vec = cv::Vec<unsigned, num_nsample_axis>;
+ using tt = std::tuple<cv_cal_vec, cv_nsample_vec>;
- static constexpr inline double yaw_spacing_in_degrees = 2.5;
- static constexpr inline double pitch_spacing_in_degrees = 1.5;
- static constexpr inline double roll_spacing_in_degrees = 3.5;
+ // get the current estimate for t_MH
+ tt get_estimate();
- unsigned yaw_rdof, pitch_rdof, roll_rdof, nsamples;
+ static constexpr double yaw_spacing_in_degrees = 2;
+ static constexpr double pitch_spacing_in_degrees = 1.5;
};
diff --git a/cv/video-widget.cpp b/cv/video-widget.cpp
index fd4fe350..7accd275 100644
--- a/cv/video-widget.cpp
+++ b/cv/video-widget.cpp
@@ -1,128 +1,64 @@
-/* Copyright (c) 2012 Patrick Ruoff
- * Copyright (c) 2014-2016 Stanislaw Halik <sthalik@misaki.pl>
- *
- * 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 "video-widget.hpp"
-#include "compat/check-visible.hpp"
-
-#include <cstring>
-
+#include "compat/macros.h"
#include <opencv2/imgproc.hpp>
-cv_video_widget::cv_video_widget(QWidget* parent) : QWidget(parent)
-{
- connect(&timer, SIGNAL(timeout()), this, SLOT(update_and_repaint()), Qt::DirectConnection);
- timer.start(65);
-}
-
void cv_video_widget::update_image(const cv::Mat& frame)
{
- QMutexLocker l(&mtx);
-
- if (!freshp)
- {
- if (width < 1 || height < 1)
- return;
-
- if (_frame.cols != frame.cols || _frame.rows != frame.rows)
- _frame = cv::Mat(frame.rows, frame.cols, CV_8UC3);
- frame.copyTo(_frame);
- freshp = true;
-
- if (_frame2.cols != _frame.cols || _frame2.rows != _frame.rows)
- _frame2 = cv::Mat(_frame.rows, _frame.cols, CV_8UC4);
-
- if (_frame3.cols != width || _frame3.rows != height)
- _frame3 = cv::Mat(height, width, CV_8UC4);
-
- cv::cvtColor(_frame, _frame2, cv::COLOR_BGR2BGRA);
-
- const cv::Mat* img;
-
- if (_frame.cols != width || _frame.rows != height)
- {
- cv::resize(_frame2, _frame3, cv::Size(width, height), 0, 0, cv::INTER_NEAREST);
-
- img = &_frame3;
- }
- else
- img = &_frame2;
-
- const unsigned nbytes = 4 * img->rows * img->cols;
-
- vec.resize(nbytes);
-
- std::memcpy(vec.data(), img->data, nbytes);
-
- texture = QImage((const unsigned char*) vec.data(), width, height, QImage::Format_ARGB32);
- }
-}
-
-void cv_video_widget::update_image(const QImage& img)
-{
- QMutexLocker l(&mtx);
-
- if (freshp)
+ if (fresh())
return;
- const unsigned nbytes = img.bytesPerLine() * img.height();
-
- vec.resize(nbytes);
-
- std::memcpy(vec.data(), img.constBits(), nbytes);
-
- texture = QImage((const unsigned char*) vec.data(), img.width(), img.height(), img.format());
-
- freshp = true;
-}
-
-void cv_video_widget::paintEvent(QPaintEvent*)
-{
- QMutexLocker foo(&mtx);
-
- QPainter painter(this);
-
- double dpr = devicePixelRatioF();
+ auto [ W, H ] = preview_size();
- int W = int(QWidget::width() * dpr);
- int H = int(QWidget::height() * dpr);
+ if (W < 1 || H < 1 || frame.rows < 1 || frame.cols < 1)
+ return;
- painter.drawImage(rect(), texture);
+ cv::Mat const* __restrict scaled = nullptr;
+ frame3.create(H, W, frame.type());
+ frame2.create(H, W, CV_8UC4);
- if (texture.width() != W || texture.height() != H)
+ if (frame.cols != W || frame.rows != H)
+ {
+ cv::resize(frame, frame3, { W, H }, 0, 0, cv::INTER_NEAREST);
+ scaled = &frame3;
+ }
+ else if (!frame.isContinuous())
{
- texture = QImage(W, H, QImage::Format_ARGB32);
- texture.setDevicePixelRatio(dpr);
+ frame.copyTo(frame3);
+ scaled = &frame3;
+ }
+ else
+ scaled = &frame;
- width = W, height = H;
+ int color_cvt = cv::COLOR_COLORCVT_MAX;
- _frame = cv::Mat();
- _frame2 = cv::Mat();
- _frame3 = cv::Mat();
+ switch (scaled->channels())
+ {
+ case 1:
+ color_cvt = cv::COLOR_GRAY2BGRA;
+ break;
+ case 3:
+ color_cvt = cv::COLOR_BGR2BGRA;
+ break;
+ case 4:
+ break;
+ default:
+ unreachable();
+ break;
}
-}
-void cv_video_widget::update_and_repaint()
-{
- if (!check_is_visible())
- return;
-
- QMutexLocker l(&mtx);
+ cv::Mat const* color;
- if (freshp)
+ if (color_cvt != cv::COLOR_COLORCVT_MAX)
{
- freshp = false;
- repaint();
+ cv::cvtColor(*scaled, frame2, color_cvt);
+ color = &frame2;
}
-}
+ else
+ color = scaled;
-void cv_video_widget::get_preview_size(int& w, int& h)
-{
- QMutexLocker l(&mtx);
+ int width = color->cols, height = color->rows;
+ unsigned stride = color->step.p[0];
+ set_image(color->data, width, height, stride, QImage::Format_ARGB32);
- w = width, h = height;
+ set_fresh(true);
}
diff --git a/cv/video-widget.hpp b/cv/video-widget.hpp
index 15430e2f..24f8d7f3 100644
--- a/cv/video-widget.hpp
+++ b/cv/video-widget.hpp
@@ -1,5 +1,4 @@
-/* Copyright (c) 2012 Patrick Ruoff
- * Copyright (c) 2014-2016 Stanislaw Halik <sthalik@misaki.pl>
+/* Copyright (c) 2019 Stanislaw Halik <sthalik@misaki.pl>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -8,39 +7,14 @@
#pragma once
-#include <memory>
-#include <vector>
+#include "video/video-widget.hpp"
+#include <opencv2/core/mat.hpp>
-#include <opencv2/core.hpp>
-
-#include <QObject>
-#include <QWidget>
-#include <QPainter>
-#include <QPaintEvent>
-#include <QTimer>
-#include <QMutex>
-#include <QMutexLocker>
-#include <QSize>
-#include <QDebug>
-
-class cv_video_widget final : public QWidget
+struct cv_video_widget final : video_widget
{
- Q_OBJECT
-
-public:
- cv_video_widget(QWidget *parent);
+ using video_widget::video_widget;
void update_image(const cv::Mat& frame);
- void update_image(const QImage& image);
- void get_preview_size(int& w, int& h);
-private slots:
- void paintEvent(QPaintEvent*) override;
- void update_and_repaint();
+
private:
- QMutex mtx { QMutex::Recursive };
- QImage texture;
- std::vector<unsigned char> vec;
- QTimer timer;
- cv::Mat _frame, _frame2, _frame3;
- int width = 320, height = 240;
- bool freshp = false;
+ cv::Mat frame2, frame3;
};
diff --git a/dinput/dinput.cpp b/dinput/dinput.cpp
index 226d3277..b9713b8a 100644
--- a/dinput/dinput.cpp
+++ b/dinput/dinput.cpp
@@ -1,88 +1,98 @@
+#undef NDEBUG
+
#include "dinput.hpp"
-#include <QDebug>
+#include "compat/macros.h"
-std::atomic<int> dinput_handle::refcnt;
-std::atomic_flag dinput_handle::init_lock = ATOMIC_FLAG_INIT;
+#include <cassert>
+#include <cstdlib>
+#include <dinput.h>
-LPDIRECTINPUT8& dinput_handle::init_di()
-{
- CoInitialize(nullptr);
+#include <QDebug>
- static LPDIRECTINPUT8 di_ = nullptr;
- if (di_ == nullptr)
- {
- if (!SUCCEEDED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&di_, NULL)))
- {
- di_ = nullptr;
- }
- }
- return di_;
-}
+diptr di_t::handle;
+QMutex di_t::lock;
-dinput_handle::di_t dinput_handle::make_di()
+diptr di_t::init_di()
{
- while (init_lock.test_and_set()) { /* busy loop */ }
+ QMutexLocker l(&lock);
- LPDIRECTINPUT8& ret = init_di();
+ CoInitialize(nullptr);
- init_lock.clear();
+ if (!handle)
+ handle = init_di_();
- return di_t(ret);
+ return handle;
}
-void dinput_handle::di_t::free_di()
+diptr di_t::operator->() const
{
- if (handle && *handle)
- {
- (*handle)->Release();
- *handle = nullptr;
- }
- handle = nullptr;
+ return init_di();
}
-void dinput_handle::di_t::ref_di()
+di_t::operator bool() const
{
- //const int refcnt_ = refcnt.fetch_add(1) + 1;
- (void) refcnt.fetch_add(1);
+ return !!init_di();
}
-dinput_handle::di_t& dinput_handle::di_t::operator=(const di_t& new_di)
+di_t::operator diptr() const
{
- if (handle)
- unref_di();
+ return init_di();
+}
- handle = new_di.handle;
+diptr di_t::init_di_()
+{
+ diptr di = nullptr;
+ HRESULT hr = DirectInput8Create(GetModuleHandle(nullptr),
+ DIRECTINPUT_VERSION,
+ IID_IDirectInput8,
+ (void**)&di,
+ nullptr);
+ if (!SUCCEEDED(hr))
+ {
+ qDebug() << "can't make dinput:" << (void*)(LONG_PTR)hr;
+ qDebug() << "crashing!";
+ std::abort();
+ }
- if (handle)
- ref_di();
+ //qDebug() << "dinput: initialized";
- return *this;
+ return di;
}
-void dinput_handle::di_t::unref_di()
+di_t::di_t() = default;
+
+bool di_t::poll_device(IDirectInputDevice8A* dev)
{
- const int refcnt_ = refcnt.fetch_sub(1) - 1;
+ HRESULT hr;
+ assert(handle);
- if (refcnt_ == 0)
+ switch (dev->Poll())
{
- while (init_lock.test_and_set()) { /* busy loop */ }
-
- qDebug() << "exit: di handle";
- free_di();
-
- init_lock.clear();
+ case DI_OK:
+ case DI_NOEFFECT:
+ return true;
+ default:
+ break;
}
-}
-dinput_handle::di_t::di_t(LPDIRECTINPUT8& handle) : handle(&handle)
-{
- ref_di();
-}
+ switch (hr = dev->Acquire())
+ {
+ default:
+ break;
+ case DI_OK:
+ case S_FALSE:
+ switch (hr = dev->Poll())
+ {
+ case DI_OK:
+ case DI_NOEFFECT:
+ return true;
+ default:
+ break;
+ }
+ break;
+ }
-dinput_handle::di_t::di_t() : handle(nullptr) {}
+ eval_once(qDebug() << "dinput: device poll failed:" << (void*)hr);
-dinput_handle::di_t::~di_t()
-{
- if (handle)
- unref_di();
+ return false;
}
diff --git a/dinput/dinput.hpp b/dinput/dinput.hpp
index fc73a90a..09c9a30b 100644
--- a/dinput/dinput.hpp
+++ b/dinput/dinput.hpp
@@ -8,44 +8,48 @@
#pragma once
+#include <QMutex>
+
#include "export.hpp"
#undef DIRECTINPUT_VERSION
#define DIRECTINPUT_VERSION 0x800
-#include <dinput.h>
-#include <atomic>
-class OTR_DINPUT_EXPORT dinput_handle final
+struct IDirectInputDevice8A;
+typedef struct IDirectInputDevice8A IDirectInputDevice8A;
+struct IDirectInput8A;
+typedef struct IDirectInput8A IDirectInput8A;
+struct _GUID;
+typedef struct _GUID GUID;
+struct _DIDATAFORMAT;
+typedef struct _DIDATAFORMAT DIDATAFORMAT;
+typedef int BOOL;
+struct DIDEVICEINSTANCEA;
+typedef struct DIDEVICEINSTANCEA DIDEVICEINSTANCEA;
+struct DIDEVICEOBJECTINSTANCEA;
+typedef struct DIDEVICEOBJECTINSTANCEA DIDEVICEOBJECTINSTANCEA;
+
+// XXX TODO -sh 20190209
+// keybinding_worker and joystick context are badly named
+// add namespaces and rename, including inner joystick device struct
+
+using diptr = IDirectInput8A*;
+
+class OTR_DINPUT_EXPORT di_t final
{
+ static diptr handle;
+ static QMutex lock;
+ static diptr init_di_();
+ static diptr init_di();
+
public:
- class di_t;
+ di_t();
+ di_t(const di_t&) : di_t() {}
+ di_t& operator=(const di_t&) = default;
-private:
- static std::atomic<int> refcnt;
- static std::atomic_flag init_lock;
+ diptr operator->() const;
+ operator bool() const;
+ operator diptr() const;
- static LPDIRECTINPUT8& init_di();
-public:
- class di_t final
- {
- friend class dinput_handle;
-
- LPDIRECTINPUT8* handle;
-
- di_t(LPDIRECTINPUT8& handle);
- void free_di();
- void unref_di();
- void ref_di();
-
- public:
- LPDIRECTINPUT8 operator->() { return *handle; }
- operator LPDIRECTINPUT8() { return *handle; }
- LPDIRECTINPUT8 di() { return *handle; }
- di_t& operator=(const di_t& new_di);
- di_t();
- ~di_t();
- };
-
- static di_t make_di();
- dinput_handle() = delete;
+ static bool poll_device(IDirectInputDevice8A* dev);
};
diff --git a/dinput/keybinding-worker.cpp b/dinput/keybinding-worker.cpp
index 30d21534..7cd8d663 100644
--- a/dinput/keybinding-worker.cpp
+++ b/dinput/keybinding-worker.cpp
@@ -9,21 +9,29 @@
#ifdef _WIN32
#include "keybinding-worker.hpp"
-#include "compat/meta.hpp"
+#include "compat/macros.h"
+#include "compat/thread-name.hpp"
#include <QDebug>
#include <QMutexLocker>
-#include <windows.h>
+#include <dinput.h>
-Key::Key() {}
+static void destroy(IDirectInputDevice8A*& dev)
+{
+ if (dev)
+ dev->Release();
+ dev = nullptr;
+}
+
+Key::Key() = default;
bool Key::should_process()
{
if (!enabled || (keycode == 0 && guid == ""))
return false;
- bool ret = prog1(!held || timer.elapsed_ms() > 100,
- timer.start());
+ bool ret = !held || timer.elapsed_ms() > 100;
+ timer.start();
return ret;
}
@@ -33,68 +41,82 @@ KeybindingWorker::~KeybindingWorker()
requestInterruption();
wait();
- if (dinkeyboard) {
- dinkeyboard->Unacquire();
- dinkeyboard->Release();
- }
+
+ destroy(dinkeyboard);
+ destroy(dinmouse);
}
-bool KeybindingWorker::init()
+bool KeybindingWorker::init_(IDirectInputDevice8A*& dev, const char* name, const GUID& guid, const DIDATAFORMAT& fmt)
{
+ if (dev)
+ return true;
+
if (!din)
{
qDebug() << "can't create dinput handle";
- return false;
+ goto fail;
}
- if (din->CreateDevice(GUID_SysKeyboard, &dinkeyboard, NULL) != DI_OK) {
- qDebug() << "setup CreateDevice function failed!" << GetLastError();
- return false;
+ if (auto hr = din->CreateDevice(guid, &dev, nullptr); hr != DI_OK)
+ {
+ qDebug() << "dinput: create" << name << "failed" << (void*)hr;
+ goto fail;
}
- if (dinkeyboard->SetDataFormat(&c_dfDIKeyboard) != DI_OK) {
- qDebug() << "setup SetDataFormat function failed!" << GetLastError();
- dinkeyboard->Release();
- dinkeyboard = 0;
- return false;
+ if (auto hr = dev->SetDataFormat(&fmt); hr != DI_OK)
+ {
+ qDebug() << "dinput:" << name << "SetDataFormat" << (void*)hr;
+ goto fail;
}
- if (dinkeyboard->SetCooperativeLevel((HWND) fake_main_window.winId(), DISCL_NONEXCLUSIVE | DISCL_BACKGROUND) != DI_OK) {
- dinkeyboard->Release();
- dinkeyboard = 0;
- qDebug() << "setup SetCooperativeLevel function failed!" << GetLastError();
- return false;
+ if (auto hr = dev->SetCooperativeLevel((HWND) fake_main_window.winId(), DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
+ hr != DI_OK)
+ {
+ qDebug() << "dinput:" << name << "SetCooperativeLevel" << (void*)hr;
+ goto fail;
}
+ return true;
+fail:
+ destroy(dev);
+ return false;
+}
+
+bool KeybindingWorker::init()
+{
+ bool ret = init_(dinkeyboard, "keyboard", GUID_SysKeyboard, c_dfDIKeyboard) &&
+ init_(dinmouse, "mouse", GUID_SysMouse, c_dfDIMouse2);
+
+ if (!ret)
+ goto fail;
+
{
DIPROPDWORD dipdw;
- dipdw.dwData = 128;
+ dipdw.dwData = num_keyboard_states;
dipdw.diph.dwHeaderSize = sizeof(dipdw.diph);
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.diph.dwObj = 0;
dipdw.diph.dwSize = sizeof(dipdw);
- if ( dinkeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph) != DI_OK)
+
+ if (auto hr = dinkeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph); hr != DI_OK)
{
- qDebug() << "setup keyboard buffer mode failed!";
- dinkeyboard->Release();
- dinkeyboard = 0;
- return false;
+ qDebug() << "dinput: keyboard DIPROP_BUFFERSIZE" << (void*)hr;
+ goto fail;
}
}
- if (dinkeyboard->Acquire() != DI_OK)
- {
- dinkeyboard->Release();
- dinkeyboard = 0;
- qDebug() << "setup dinkeyboard Acquire failed!" << GetLastError();
- return false;
- }
-
return true;
+
+fail:
+ destroy(dinkeyboard);
+ destroy(dinmouse);
+ return false;
}
-KeybindingWorker::KeybindingWorker() : dinkeyboard(nullptr), din(dinput_handle::make_di())
+KeybindingWorker::KeybindingWorker()
{
+ fake_main_window.setAttribute(Qt::WA_NativeWindow);
+
if (init())
start(QThread::HighPriority);
}
@@ -107,102 +129,156 @@ KeybindingWorker& KeybindingWorker::make()
void KeybindingWorker::run()
{
+ portable::set_curthread_name("keybinding worker");
+
while (!isInterruptionRequested())
{
{
QMutexLocker l(&mtx);
- if (receivers.size())
+ if (!receivers.empty())
{
- /* There are some problems reported on various forums
- * with regard to key-up events. But that's what I dug up:
- *
- * https://www.gamedev.net/forums/topic/633011-keyboard-getdevicedata-buffered-never-releases-keys/
- *
- * "Over in the xna forums (http://xboxforums.create.msdn.com/forums/p/108722/642144.aspx#642144)
- * we discovered this behavior is caused by calling Unacquire in your event processing loop.
- * Funnily enough only the keyboard seems to be affected."
- *
- * Key-up events work on my end.
- */
-
- {
- DWORD sz = num_keyboard_states;
- const HRESULT hr = dinkeyboard->GetDeviceData(sizeof(*keyboard_states), keyboard_states, &sz, 0);
-
- if (hr != DI_OK)
- {
- qDebug() << "Tracker::run GetDeviceData function failed!" << hr;
- Sleep(25);
- continue;
- }
- else
- {
- for (unsigned k = 0; k < sz; k++)
- {
- const unsigned idx = keyboard_states[k].dwOfs & 0xff; // defensive programming
- const bool held = !!(keyboard_states[k].dwData & 0x80);
-
- switch (idx)
- {
- case DIK_LCONTROL:
- case DIK_LSHIFT:
- case DIK_LALT:
- case DIK_RCONTROL:
- case DIK_RSHIFT:
- case DIK_RALT:
- case DIK_LWIN:
- case DIK_RWIN:
- break;
- default:
- {
- Key k;
- k.shift = keystate[DIK_LSHIFT] | keystate[DIK_RSHIFT];
- k.alt = keystate[DIK_LALT] | keystate[DIK_RALT];
- k.ctrl = keystate[DIK_LCONTROL] | keystate[DIK_RCONTROL];
- k.keycode = idx;
- k.held = held;
-
- for (auto& r : receivers)
- (*r)(k);
- break;
- }
- }
- keystate[idx] = held;
- }
- }
- }
-
- {
- using joy_fn = std::function<void(const QString& guid, int idx, bool held)>;
-
- joy_fn f = [&](const QString& guid, int idx, bool held) {
- Key k;
- k.keycode = idx;
- k.shift = keystate[DIK_LSHIFT] | keystate[DIK_RSHIFT];
- k.alt = keystate[DIK_LALT] | keystate[DIK_RALT];
- k.ctrl = keystate[DIK_LCONTROL] | keystate[DIK_RCONTROL];
- k.guid = guid;
- k.held = held;
-
- for (auto& r : receivers)
- (*r)(k);
- };
-
- joy_ctx.poll(f);
- }
+ bool ok = true;
+
+ ok &= run_keyboard_nolock();
+ ok &= run_mouse_nolock();
+ ok &= run_joystick_nolock();
+
+ if (!ok)
+ Sleep(500);
}
}
- Sleep(100);
+ Sleep(25);
}
}
-KeybindingWorker::fun* KeybindingWorker::_add_receiver(fun& receiver)
+bool KeybindingWorker::run_mouse_nolock()
+{
+ DIMOUSESTATE2 state;
+
+ if (!di_t::poll_device(dinmouse))
+ eval_once(qDebug() << "dinput: mouse poll failed");
+
+ if (auto hr = dinmouse->GetDeviceState(sizeof(state), &state); hr != DI_OK)
+ {
+ eval_once(qDebug() << "dinput: mouse GetDeviceState failed" << (void*) hr << GetLastError);
+ return false;
+ }
+
+ Key k;
+ k.guid = QStringLiteral("mouse");
+
+ for (int i = first_mouse_button; i < num_mouse_buttons; i++)
+ {
+ const bool new_state = state.rgbButtons[i] & 0x80;
+ k.held = new_state;
+ k.keycode = i;
+ bool& old_state = mouse_state[i - first_mouse_button];
+ if (old_state != new_state)
+ {
+ for (auto& r : receivers)
+ (*r)(k);
+ }
+ old_state = new_state;
+ }
+ return true;
+}
+
+bool KeybindingWorker::run_keyboard_nolock()
+{
+ /* There are some problems reported on various forums
+ * with regard to key-up events. But that's what I dug up:
+ *
+ * https://www.gamedev.net/forums/topic/633011-keyboard-getdevicedata-buffered-never-releases-keys/
+ *
+ * "Over in the xna forums (http://xboxforums.create.msdn.com/forums/p/108722/642144.aspx#642144)
+ * we discovered this behavior is caused by calling Unacquire in your event processing loop.
+ * Funnily enough only the keyboard seems to be affected."
+ *
+ * Key-up events work on my end.
+ */
+
+ if (!di_t::poll_device(dinkeyboard))
+ eval_once(qDebug() << "dinput: keyboard poll failed");
+
+ DIDEVICEOBJECTDATA keyboard_states[num_keyboard_states];
+
+ DWORD sz = num_keyboard_states;
+ HRESULT hr = dinkeyboard->GetDeviceData(sizeof(*keyboard_states), keyboard_states, &sz, 0);
+
+ if (FAILED(hr))
+ {
+ eval_once(qDebug() << "dinput: keyboard GetDeviceData failed" << (void*)hr);
+ return false;
+ }
+
+ for (unsigned k = 0; k < sz; k++)
+ {
+ const int idx = keyboard_states[k].dwOfs & 0xff; // defensive programming
+ const bool held = !!(keyboard_states[k].dwData & 0x80);
+
+ if (held == keystate[idx])
+ continue;
+ keystate[idx] = held;
+
+ switch (idx)
+ {
+ case DIK_LCONTROL:
+ case DIK_LSHIFT:
+ case DIK_LALT:
+ case DIK_RCONTROL:
+ case DIK_RSHIFT:
+ case DIK_RALT:
+ case DIK_LWIN:
+ case DIK_RWIN:
+ break;
+ default:
+ {
+ Key k;
+ k.shift = keystate[DIK_LSHIFT] | keystate[DIK_RSHIFT];
+ k.alt = keystate[DIK_LALT] | keystate[DIK_RALT];
+ k.ctrl = keystate[DIK_LCONTROL] | keystate[DIK_RCONTROL];
+ k.keycode = idx;
+ k.held = held;
+
+ for (auto& r : receivers)
+ (*r)(k);
+ }
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool KeybindingWorker::run_joystick_nolock()
+{
+ using joy_fn = std::function<void(const QString& guid, int idx, bool held)>;
+
+ joy_fn f = [&](const QString& guid, int idx, bool held) {
+ Key k;
+ k.keycode = idx;
+ k.shift = keystate[DIK_LSHIFT] | keystate[DIK_RSHIFT];
+ k.alt = keystate[DIK_LALT] | keystate[DIK_RALT];
+ k.ctrl = keystate[DIK_LCONTROL] | keystate[DIK_RCONTROL];
+ k.guid = guid;
+ k.held = held;
+
+ for (auto& r : receivers)
+ (*r)(k);
+ };
+
+ joy_ctx.poll(f);
+
+ return true;
+}
+
+KeybindingWorker::fun* KeybindingWorker::add_receiver(fun& receiver)
{
QMutexLocker l(&mtx);
receivers.push_back(std::make_unique<fun>(receiver));
- fun* f = receivers[receivers.size() - 1].get();
+ fun* f = &*receivers[receivers.size() - 1];
//qDebug() << "add receiver" << (long) f;
joy_ctx.refresh();
return f;
@@ -218,7 +294,7 @@ void KeybindingWorker::remove_receiver(KeybindingWorker::fun* pos)
for (int i = s(receivers.size()) - 1; i >= 0; i--)
{
using u = unsigned;
- if (receivers[u(i)].get() == pos)
+ if (&*receivers[u(i)] == pos)
{
ok = true;
//qDebug() << "remove receiver" << (long) pos;
@@ -228,7 +304,7 @@ void KeybindingWorker::remove_receiver(KeybindingWorker::fun* pos)
}
if (!ok)
{
- qDebug() << "bad remove receiver" << (long) pos;
+ qDebug() << "bad remove receiver" << (void*) pos;
}
}
diff --git a/dinput/keybinding-worker.hpp b/dinput/keybinding-worker.hpp
index 407b0107..1c22ca17 100644
--- a/dinput/keybinding-worker.hpp
+++ b/dinput/keybinding-worker.hpp
@@ -24,13 +24,13 @@
struct OTR_DINPUT_EXPORT Key
{
QString guid;
+ Timer timer;
int keycode = 0;
bool shift = false;
bool ctrl = false;
bool alt = false;
bool held = true;
bool enabled = true;
- Timer timer;
public:
Key();
@@ -41,45 +41,52 @@ struct OTR_DINPUT_EXPORT KeybindingWorker : private QThread
{
using fun = std::function<void(const Key&)>;
+ KeybindingWorker(const KeybindingWorker&) = delete;
+ KeybindingWorker& operator=(KeybindingWorker&) = delete;
+
private:
- LPDIRECTINPUTDEVICE8 dinkeyboard;
+ static constexpr int num_keyboard_states = 64;
+ static constexpr int num_mouse_buttons = 8;
+ static constexpr int first_mouse_button = 3;
+
+ IDirectInputDevice8A* dinkeyboard = nullptr, *dinmouse = nullptr;
win32_joy_ctx joy_ctx;
std::vector<std::unique_ptr<fun>> receivers;
QMutex mtx;
QMainWindow fake_main_window;
- dinput_handle::di_t din;
+ di_t din;
bool keystate[256] {};
- bool old_keystate[256] {};
+ bool mouse_state[num_mouse_buttons - first_mouse_button] = {};
void run() override;
+ bool run_keyboard_nolock();
+ bool run_joystick_nolock();
+ bool run_mouse_nolock();
+
bool init();
+ bool init_(IDirectInputDevice8A*& dev, const char* name, const GUID& guid, const DIDATAFORMAT& fmt);
KeybindingWorker();
static KeybindingWorker& make();
- fun* _add_receiver(fun &receiver);
+ fun* add_receiver(fun& receiver);
void remove_receiver(fun* pos);
- ~KeybindingWorker();
-
- static constexpr int num_keyboard_states = 128;
- DIDEVICEOBJECTDATA keyboard_states[num_keyboard_states];
-
- KeybindingWorker(const KeybindingWorker&) = delete;
- KeybindingWorker& operator=(KeybindingWorker&) = delete;
+ ~KeybindingWorker() override;
public:
class Token
{
fun* pos;
+ public:
Token(const Token&) = delete;
Token& operator=(Token&) = delete;
- public:
+
~Token()
{
make().remove_receiver(pos);
}
Token(fun receiver)
{
- pos = make()._add_receiver(receiver);
+ pos = make().add_receiver(receiver);
}
};
};
diff --git a/dinput/lang/zh_CN.ts b/dinput/lang/zh_CN.ts
index 6401616d..e5ca8aa9 100644
--- a/dinput/lang/zh_CN.ts
+++ b/dinput/lang/zh_CN.ts
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
</TS>
diff --git a/dinput/win32-joystick.cpp b/dinput/win32-joystick.cpp
index 9127b6a8..cb7a3837 100644
--- a/dinput/win32-joystick.cpp
+++ b/dinput/win32-joystick.cpp
@@ -1,40 +1,39 @@
#ifdef _WIN32
-#undef NDEBUG
#include "win32-joystick.hpp"
-#include "compat/sleep.hpp"
-#include <cassert>
-#include <cstring>
+#include "compat/macros.h"
+
+#include <cstddef>
#include <algorithm>
#include <cmath>
-#include <objbase.h>
+#include <iterator>
+#include <QWidget>
#include <QDebug>
-// XXX how many axis update events can we reasonably get in a short time frame?
-enum { num_buffers = 256 };
-DIDEVICEOBJECTDATA win32_joy_ctx::joy::keystate_buffers[num_buffers];
+#include <dinput.h>
+#include <objbase.h>
+
+namespace win32_joy_impl {
QMutex win32_joy_ctx::enum_state::mtx;
win32_joy_ctx::enum_state win32_joy_ctx::enumerator;
-void win32_joy_ctx::poll(fn f)
+void win32_joy_ctx::poll(fn const& f)
{
//refresh(false);
- QMutexLocker l(&enumerator.mtx);
+ QMutexLocker l(&enum_state::mtx);
auto& joys = enumerator.get_joys();
for (auto& j : joys)
- {
j.second->poll(f);
- }
}
bool win32_joy_ctx::poll_axis(const QString &guid, int* axes)
{
- QMutexLocker l(&enumerator.mtx);
+ QMutexLocker l(&enum_state::mtx);
for (int k = 0; k < 10; k++)
{
@@ -48,35 +47,15 @@ bool win32_joy_ctx::poll_axis(const QString &guid, int* axes)
return false;
auto& j = iter->second;
-
auto& joy_handle = j->joy_handle;
- bool ok = false;
- HRESULT hr;
-
- if (!FAILED(hr = joy_handle->Poll()))
- {
- ok = true;
- }
-
- if (!ok && FAILED(hr = joy_handle->Acquire()))
- {
- //qDebug() << "joy acquire failed" << hr;
- }
+ DIJOYSTATE2 js;
- if (!ok)
- {
- portable::sleep(25);
- (void) joy_handle->Unacquire();
+ if (!di_t::poll_device(joy_handle))
continue;
- }
- DIJOYSTATE2 js;
- std::memset(&js, 0, sizeof(js));
-
- if (FAILED(hr = joy_handle->GetDeviceState(sizeof(js), &js)))
+ if (FAILED(joy_handle->GetDeviceState(sizeof(js), &js)))
{
- //qDebug() << "joy get state failed" << guid << hr;
- portable::sleep(50);
+ //qDebug() << "joy get state failed" << guid;
continue;
}
@@ -92,7 +71,7 @@ bool win32_joy_ctx::poll_axis(const QString &guid, int* axes)
js.rglSlider[1]
};
- for (int i = 0; i < 8; i++)
+ for (unsigned i = 0; i < std::size(values); i++)
axes[i] = values[i];
return true;
@@ -104,7 +83,7 @@ bool win32_joy_ctx::poll_axis(const QString &guid, int* axes)
std::vector<win32_joy_ctx::joy_info> win32_joy_ctx::get_joy_info()
{
std::vector<joy_info> ret;
- QMutexLocker l(&enumerator.mtx);
+ QMutexLocker l(&enum_state::mtx);
auto& joys = enumerator.get_joys();
ret.reserve(joys.size());
@@ -123,17 +102,18 @@ win32_joy_ctx::win32_joy_ctx()
void win32_joy_ctx::refresh()
{
- QMutexLocker l(&enumerator.mtx);
+ QMutexLocker l(&enum_state::mtx);
enumerator.refresh();
}
QString win32_joy_ctx::guid_to_string(const GUID& guid)
{
- char buf[40] = {0};
- wchar_t szGuidW[40] = {0};
+ char buf[40] = {};
+ wchar_t szGuidW[40] = {};
- StringFromGUID2(guid, szGuidW, 40);
- WideCharToMultiByte(0, 0, szGuidW, -1, buf, 40, NULL, NULL);
+ StringFromGUID2(guid, szGuidW, sizeof(buf));
+ WideCharToMultiByte(0, 0, szGuidW, -1, buf, sizeof(buf), nullptr, nullptr);
+ buf[sizeof(buf)-1] = 0;
return QString(buf);
}
@@ -150,27 +130,24 @@ void win32_joy_ctx::joy::release()
}
}
-bool win32_joy_ctx::joy::poll(fn f)
+bool win32_joy_ctx::joy::poll(fn const& f)
{
HRESULT hr;
- bool ok = false;
-
- (void) joy_handle->Acquire();
- if (!FAILED(hr = joy_handle->Poll()))
- ok = true;
-
- if (!ok)
+ if (!di_t::poll_device(joy_handle))
{
- //qDebug() << "joy acquire failed" << guid << hr;
- (void) joy_handle->Unacquire();
+ eval_once(qDebug() << "joy poll failed" << guid << (void*)hr);
+ //(void)joy_handle->Unacquire();
+ //Sleep(0);
return false;
}
+ DIDEVICEOBJECTDATA keystate_buffers[num_buffers];
+
DWORD sz = num_buffers;
if (FAILED(hr = joy_handle->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), keystate_buffers, &sz, 0)))
{
- //qDebug() << "joy get state failed" << guid << hr;
+ eval_once(qDebug() << "joy GetDeviceData failed" << guid << (void*)hr);
return false;
}
@@ -181,74 +158,65 @@ bool win32_joy_ctx::joy::poll(fn f)
bool is_pov = false;
int i = -1;
+#define POV_HAT_OFFSET(k) \
+ (offsetof(DIJOYSTATE2, rgdwPOV) + (k) * sizeof(DWORD))
+#define BUTTON_OFFSET(k) \
+ (offsetof(DIJOYSTATE2, rgbButtons) + (k) * sizeof(BYTE))
+
switch (event.dwOfs)
{
- case DIJOFS_POV(0): i = 0, is_pov = true; break;
- case DIJOFS_POV(2): i = 1, is_pov = true; break;
- case DIJOFS_POV(3): i = 2, is_pov = true; break;
- case DIJOFS_POV(4): i = 3, is_pov = true; break;
+ case POV_HAT_OFFSET(0): i = 0; is_pov = true; break;
+ case POV_HAT_OFFSET(1): i = 1; is_pov = true; break;
+ case POV_HAT_OFFSET(2): i = 2; is_pov = true; break;
+ case POV_HAT_OFFSET(3): i = 3; is_pov = true; break;
default:
- if (event.dwOfs >= DIJOFS_BUTTON0 && event.dwOfs <= DIJOFS_BUTTON(127))
+ if (event.dwOfs >= BUTTON_OFFSET(0) && event.dwOfs <= BUTTON_OFFSET(max_buttons - 1))
{
- unsigned tmp = event.dwOfs;
- tmp -= DIJOFS_BUTTON0;
- tmp /= DIJOFS_BUTTON1 - DIJOFS_BUTTON0;
- tmp &= 127;
- i = tmp;
+ i = int(event.dwOfs - BUTTON_OFFSET(0));
+ i /= sizeof(DIJOYSTATE2().rgbButtons[0]);
+ i %= max_buttons; // defensive programming
}
break;
}
if (is_pov)
{
- //qDebug() << "DBG: pov" << i << event.dwData;
-
- using std::round;
+ unsigned pos = event.dwData / value_per_pov_hat_direction;
- unsigned char pos;
- unsigned pos_ = event.dwData;
- if ((pos_ & 0xffff) == 0xffff)
- pos = 0;
- else if (pos_ == ~0u)
- pos = 0;
- else
- {
- using uc = unsigned char;
- pos = uc(((pos_ / 9000u) % 4u) + 1u);
- }
+ i = max_buttons + i * pov_hat_directions;
- const bool state[] =
+ for (unsigned j = 0; j < pov_hat_directions; j++)
{
- pos == 1,
- pos == 2,
- pos == 3,
- pos == 4
- };
-
- i = 128u + i * 4u;
-
- for (unsigned j = 0; j < 4; j++)
- {
- //pressed[i] = state[j];
- f(guid, i, state[j]);
+ const unsigned idx = i + j;
+ const bool new_value = pos == j;
+ if (last_state[idx] != new_value)
+ {
+#ifdef WIN32_JOY_DEBUG
+ qDebug() << "DBG: pov" << idx << (pos == j);
+#endif
+ last_state[idx] = new_value;
+ f(guid, idx, new_value);
+ }
}
}
- else if (i != -1)
+ else if ((unsigned)i < max_buttons)
{
- const bool state = !!(event.dwData & 0x80);
- //qDebug() << "DBG: btn" << i << state;
- //pressed[i] = state;
- f(guid, i, state);
+ const bool new_value = !!(event.dwData & 0x80);
+ if (last_state[i] != new_value)
+ {
+#ifdef WIN32_JOY_DEBUG
+ qDebug() << "DBG: btn" << i << new_value;
+#endif
+ last_state[i] = new_value;
+ f(guid, i, new_value);
+ }
}
-
}
return true;
}
-win32_joy_ctx::enum_state::enum_state() : di(dinput_handle::make_di())
-{
-}
+win32_joy_ctx::enum_state::enum_state() = default;
win32_joy_ctx::enum_state::~enum_state()
{
@@ -260,10 +228,13 @@ win32_joy_ctx::enum_state::~enum_state()
void win32_joy_ctx::enum_state::refresh()
{
all.clear();
+#ifdef __SANITIZE_ADDRESS__
+ return;
+#endif
if (!di)
{
- qDebug() << "can't create dinput";
+ qDebug() << "dinput: can't create dinput";
return;
}
@@ -274,20 +245,16 @@ void win32_joy_ctx::enum_state::refresh()
this,
DIEDFL_ATTACHEDONLY)))
{
- qDebug() << "failed enum joysticks" << hr;
+ eval_once(qDebug() << "dinput: failed enum joysticks" << (void*)hr);
return;
}
for (auto it = joys.begin(); it != joys.end(); )
{
if (std::find_if(all.cbegin(), all.cend(), [&](const QString& guid2) { return it->second->guid == guid2; }) == all.end())
- {
it = joys.erase(it);
- }
else
- {
++it;
- }
}
}
@@ -308,20 +275,20 @@ BOOL CALLBACK win32_joy_ctx::enum_state::EnumJoysticksCallback(const DIDEVICEINS
{
HRESULT hr;
- LPDIRECTINPUTDEVICE8 h;
+ IDirectInputDevice8A* h;
if (FAILED(hr = state.di->CreateDevice(pdidInstance->guidInstance, &h, nullptr)))
{
- qDebug() << "createdevice" << guid << hr;
+ qDebug() << "dinput: failed joystick CreateDevice" << guid << (void*)hr;
goto end;
}
- if (FAILED(h->SetDataFormat(&c_dfDIJoystick2)))
+ if (FAILED(hr = h->SetDataFormat(&c_dfDIJoystick2)))
{
- qDebug() << "format";
+ qDebug() << "dinput: failed joystick SetDataFormat" << (void*)hr;
h->Release();
goto end;
}
- // not a static member - need main() to run for some time first
+ // not a library-load-time member - need main() to run for some time first
static const QWidget fake_window;
if (FAILED(h->SetCooperativeLevel(reinterpret_cast<HWND>(fake_window.winId()), DISCL_NONEXCLUSIVE | DISCL_BACKGROUND)))
@@ -332,16 +299,16 @@ BOOL CALLBACK win32_joy_ctx::enum_state::EnumJoysticksCallback(const DIDEVICEINS
}
{
- DIPROPDWORD dipdw;
- dipdw.dwData = 128;
+ DIPROPDWORD dipdw = {};
dipdw.diph.dwHeaderSize = sizeof(dipdw.diph);
+ dipdw.diph.dwSize = sizeof(dipdw);
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.diph.dwObj = 0;
- dipdw.diph.dwSize = sizeof(dipdw);
+ dipdw.dwData = num_buffers;
if (h->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph) != DI_OK)
{
- qDebug() << "setup joystick buffer mode failed!";
+ qDebug() << "dinput: joystick DIPROP_BUFFERSIZE";
h->Release();
goto end;
}
@@ -349,7 +316,7 @@ BOOL CALLBACK win32_joy_ctx::enum_state::EnumJoysticksCallback(const DIDEVICEINS
if (FAILED(hr = h->EnumObjects(EnumObjectsCallback, h, DIDFT_ALL)))
{
- qDebug() << "enum-objects";
+ qDebug() << "dinput: joystick EnumObjects";
h->Release();
goto end;
}
@@ -364,10 +331,9 @@ BOOL CALLBACK win32_joy_ctx::enum_state::EnumObjectsCallback(const DIDEVICEOBJEC
{
if (pdidoi->dwType & DIDFT_AXIS)
{
- DIPROPRANGE diprg;
- std::memset(&diprg, 0, sizeof(diprg));
- diprg.diph.dwSize = sizeof( DIPROPRANGE );
- diprg.diph.dwHeaderSize = sizeof( DIPROPHEADER );
+ DIPROPRANGE diprg = {};
+ diprg.diph.dwSize = sizeof(DIPROPRANGE);
+ diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
diprg.diph.dwHow = DIPH_BYID;
diprg.diph.dwObj = pdidoi->dwType;
diprg.lMax = joy_axis_size;
@@ -375,9 +341,9 @@ BOOL CALLBACK win32_joy_ctx::enum_state::EnumObjectsCallback(const DIDEVICEOBJEC
HRESULT hr;
- if (FAILED(hr = reinterpret_cast<LPDIRECTINPUTDEVICE8>(ctx)->SetProperty(DIPROP_RANGE, &diprg.diph)))
+ if (FAILED(hr = reinterpret_cast<IDirectInputDevice8A*>(ctx)->SetProperty(DIPROP_RANGE, &diprg.diph)))
{
- qDebug() << "DIPROP_RANGE" << hr;
+ qDebug() << "dinput: failed joystick DIPROP_RANGE" << (void*)hr;
return DIENUM_STOP;
}
}
@@ -385,7 +351,7 @@ BOOL CALLBACK win32_joy_ctx::enum_state::EnumObjectsCallback(const DIDEVICEOBJEC
return DIENUM_CONTINUE;
}
-win32_joy_ctx::joy::joy(LPDIRECTINPUTDEVICE8 handle, const QString &guid, const QString &name)
+win32_joy_ctx::joy::joy(IDirectInputDevice8A* handle, const QString& guid, const QString &name)
: joy_handle(handle), guid(guid), name(name)
{
//qDebug() << "make joy" << guid << name << joy_handle;
@@ -397,4 +363,5 @@ win32_joy_ctx::joy::~joy()
release();
}
+} // ns win32_joy_impl
#endif
diff --git a/dinput/win32-joystick.hpp b/dinput/win32-joystick.hpp
index 42ecf57f..8e5344d6 100644
--- a/dinput/win32-joystick.hpp
+++ b/dinput/win32-joystick.hpp
@@ -7,65 +7,67 @@
*/
#pragma once
-#ifdef _WIN32
-
#include "dinput.hpp"
#include "compat/timer.hpp"
#include "export.hpp"
-#include <cstring>
+#include "compat/qhash.hpp"
+
#include <memory>
#include <vector>
#include <functional>
-#include <algorithm>
#include <unordered_map>
+#include <iterator>
+
#include <QString>
-#include <QDebug>
#include <QMutex>
-#include <QMutexLocker>
-#include <QWidget>
-namespace std {
-template<>
-struct hash<QString>
-{
- inline std::size_t operator()(const QString& value) const
- {
- return qHash(value);
- }
-};
-}
+namespace win32_joy_impl {
+
+static constexpr unsigned max_buttons = 128;
+static constexpr unsigned max_pov_hats = 4;
+static constexpr unsigned pov_hat_directions = 8;
+
+// cf. https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416628(v=vs.85)
+// see also remarks on the page
+// no need to check for pos == unsigned(-1) || pos == 0xffff,
+// this logic doesn't require that
+static constexpr unsigned value_per_pov_hat_direction = 36000 / pov_hat_directions;
+static constexpr unsigned max_buttons_and_pov_hats = max_buttons + max_pov_hats * pov_hat_directions;
-struct OTR_DINPUT_EXPORT win32_joy_ctx
+//static_assert(pov_hat_directions == 4 || pov_hat_directions == 8);
+
+// XXX how many axis update events can we reasonably get in a short time frame?
+static constexpr unsigned num_buffers = 16;
+
+//#define WIN32_JOY_DEBUG
+
+struct OTR_DINPUT_EXPORT win32_joy_ctx final
{
using fn = std::function<void(const QString& guid, int btn, bool held)>;
- struct joy
+ struct joy final
{
- LPDIRECTINPUTDEVICE8 joy_handle;
+ IDirectInputDevice8A* joy_handle;
QString guid, name;
- enum { num_pressed_keys = 128 + 4 * 4 };
- //bool pressed[num_pressed_keys] {};
- Timer first_timer;
-
- static DIDEVICEOBJECTDATA keystate_buffers[256];
+ bool last_state[max_buttons_and_pov_hats] {};
- joy(LPDIRECTINPUTDEVICE8 handle, const QString& guid, const QString& name);
+ joy(IDirectInputDevice8A* handle, const QString& guid, const QString& name);
~joy();
void release();
- bool poll(fn f);
+ bool poll(fn const& f);
};
using joys_t = std::unordered_map<QString, std::shared_ptr<joy>>;
- static constexpr inline int joy_axis_size = 65536;
+ static constexpr int joy_axis_size = 65536;
struct joy_info
{
QString name, guid;
};
- void poll(fn f);
+ void poll(fn const& f);
bool poll_axis(const QString& guid, int* axes);
std::vector<joy_info> get_joy_info();
@@ -75,8 +77,6 @@ struct OTR_DINPUT_EXPORT win32_joy_ctx
win32_joy_ctx();
void refresh();
- using di_t = dinput_handle::di_t;
-
private:
static QString guid_to_string(const GUID& guid);
@@ -84,10 +84,10 @@ private:
{
std::vector<QString> all;
joys_t joys;
- dinput_handle::di_t di;
+ di_t di;
- static BOOL CALLBACK EnumJoysticksCallback(const DIDEVICEINSTANCE* pdidInstance, VOID* pContext);
- static BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* ctx);
+ static BOOL __stdcall EnumJoysticksCallback(const DIDEVICEINSTANCEA* pdidInstance, void* pContext);
+ static BOOL __stdcall EnumObjectsCallback(const DIDEVICEOBJECTINSTANCEA* pdidoi, void* ctx);
public:
static QMutex mtx;
@@ -96,9 +96,13 @@ private:
~enum_state();
void refresh();
const joys_t& get_joys() const;
+
+ enum_state(enum_state const&) = delete;
};
static enum_state enumerator;
};
-#endif
+} // ns win32_joy_impl
+
+using win32_joy_ctx = win32_joy_impl::win32_joy_ctx;
diff --git a/docs/.gitignore b/docs/.gitignore
deleted file mode 100644
index 84c048a7..00000000
--- a/docs/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build/
diff --git a/docs/doxygen.conf b/docs/doxygen.conf
deleted file mode 100644
index 68ba2dd9..00000000
--- a/docs/doxygen.conf
+++ /dev/null
@@ -1,2469 +0,0 @@
-# Doxyfile 1.8.13
-
-# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project.
-#
-# All text after a double hash (##) is considered a comment and is placed in
-# front of the TAG it is preceding.
-#
-# All text after a single hash (#) is considered a comment and will be ignored.
-# The format is:
-# TAG = value [value, ...]
-# For lists, items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (\" \").
-
-#---------------------------------------------------------------------------
-# Project related configuration options
-#---------------------------------------------------------------------------
-
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all text
-# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
-# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
-# for the list of possible encodings.
-# The default value is: UTF-8.
-
-DOXYFILE_ENCODING = UTF-8
-
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
-# double-quotes, unless you are using Doxywizard) that should identify the
-# project for which the documentation is generated. This name is used in the
-# title of most generated pages and in a few other places.
-# The default value is: My Project.
-
-PROJECT_NAME = "opentrack"
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
-# could be handy for archiving the generated documentation or if some version
-# control system is used.
-
-PROJECT_NUMBER = 2.4.0
-
-# Using the PROJECT_BRIEF tag one can provide an optional one line description
-# for a project that appears at the top of each page and should give viewer a
-# quick idea about the purpose of the project. Keep the description short.
-
-PROJECT_BRIEF =
-
-# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
-# in the documentation. The maximum height of the logo should not exceed 55
-# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
-# the logo to the output directory.
-
-PROJECT_LOGO =
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
-# into which the generated documentation will be written. If a relative path is
-# entered, it will be relative to the location where doxygen was started. If
-# left blank the current directory will be used.
-
-OUTPUT_DIRECTORY = ../../opentrack/docs/build/
-
-# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
-# directories (in 2 levels) under the output directory of each output format and
-# will distribute the generated files over these directories. Enabling this
-# option can be useful when feeding doxygen a huge amount of source files, where
-# putting all generated files in the same directory would otherwise causes
-# performance problems for the file system.
-# The default value is: NO.
-
-CREATE_SUBDIRS = NO
-
-# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
-# characters to appear in the names of generated files. If set to NO, non-ASCII
-# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
-# U+3044.
-# The default value is: NO.
-
-ALLOW_UNICODE_NAMES = NO
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
-# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
-# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
-# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
-# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
-# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
-# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
-# Ukrainian and Vietnamese.
-# The default value is: English.
-
-OUTPUT_LANGUAGE = English
-
-# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
-# descriptions after the members that are listed in the file and class
-# documentation (similar to Javadoc). Set to NO to disable this.
-# The default value is: YES.
-
-BRIEF_MEMBER_DESC = YES
-
-# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
-# description of a member or function before the detailed description
-#
-# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-# The default value is: YES.
-
-REPEAT_BRIEF = YES
-
-# This tag implements a quasi-intelligent brief description abbreviator that is
-# used to form the text in various listings. Each string in this list, if found
-# as the leading text of the brief description, will be stripped from the text
-# and the result, after processing the whole list, is used as the annotated
-# text. Otherwise, the brief description is used as-is. If left blank, the
-# following values are used ($name is automatically replaced with the name of
-# the entity):The $name class, The $name widget, The $name file, is, provides,
-# specifies, contains, represents, a, an and the.
-
-ABBREVIATE_BRIEF = "The $name class" \
- "The $name widget" \
- "The $name file" \
- is \
- provides \
- specifies \
- contains \
- represents \
- a \
- an \
- the
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# doxygen will generate a detailed section even if there is only a brief
-# description.
-# The default value is: NO.
-
-ALWAYS_DETAILED_SEC = NO
-
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
-# operators of the base classes will not be shown.
-# The default value is: NO.
-
-INLINE_INHERITED_MEMB = NO
-
-# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
-# before files name in the file list and in the header files. If set to NO the
-# shortest path that makes the file name unique will be used
-# The default value is: YES.
-
-FULL_PATH_NAMES = NO
-
-# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
-# Stripping is only done if one of the specified strings matches the left-hand
-# part of the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the path to
-# strip.
-#
-# Note that you can specify absolute paths here, but also relative paths, which
-# will be relative from the directory where doxygen is started.
-# This tag requires that the tag FULL_PATH_NAMES is set to YES.
-
-STRIP_FROM_PATH =
-
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
-# path mentioned in the documentation of a class, which tells the reader which
-# header file to include in order to use a class. If left blank only the name of
-# the header file containing the class definition is used. Otherwise one should
-# specify the list of include paths that are normally passed to the compiler
-# using the -I flag.
-
-STRIP_FROM_INC_PATH =
-
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
-# less readable) file names. This can be useful is your file systems doesn't
-# support long names like on DOS, Mac, or CD-ROM.
-# The default value is: NO.
-
-SHORT_NAMES = NO
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
-# first line (until the first dot) of a Javadoc-style comment as the brief
-# description. If set to NO, the Javadoc-style will behave just like regular Qt-
-# style comments (thus requiring an explicit @brief command for a brief
-# description.)
-# The default value is: NO.
-
-JAVADOC_AUTOBRIEF = NO
-
-# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
-# line (until the first dot) of a Qt-style comment as the brief description. If
-# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
-# requiring an explicit \brief command for a brief description.)
-# The default value is: NO.
-
-QT_AUTOBRIEF = NO
-
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
-# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
-# a brief description. This used to be the default behavior. The new default is
-# to treat a multi-line C++ comment block as a detailed description. Set this
-# tag to YES if you prefer the old behavior instead.
-#
-# Note that setting this tag to YES also means that rational rose comments are
-# not recognized any more.
-# The default value is: NO.
-
-MULTILINE_CPP_IS_BRIEF = NO
-
-# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
-# documentation from any documented member that it re-implements.
-# The default value is: YES.
-
-INHERIT_DOCS = NO
-
-# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
-# page for each member. If set to NO, the documentation of a member will be part
-# of the file/class/namespace that contains it.
-# The default value is: NO.
-
-SEPARATE_MEMBER_PAGES = NO
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
-# uses this value to replace tabs by spaces in code fragments.
-# Minimum value: 1, maximum value: 16, default value: 4.
-
-TAB_SIZE = 4
-
-# This tag can be used to specify a number of aliases that act as commands in
-# the documentation. An alias has the form:
-# name=value
-# For example adding
-# "sideeffect=@par Side Effects:\n"
-# will allow you to put the command \sideeffect (or @sideeffect) in the
-# documentation, which will result in a user-defined paragraph with heading
-# "Side Effects:". You can put \n's in the value part of an alias to insert
-# newlines.
-
-ALIASES =
-
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding "class=itcl::class"
-# will allow you to use the command class in the itcl::class meaning.
-
-TCL_SUBST =
-
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
-# only. Doxygen will then generate output that is more tailored for C. For
-# instance, some of the names that are used will be different. The list of all
-# members will be omitted, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_FOR_C = NO
-
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
-# Python sources only. Doxygen will then generate output that is more tailored
-# for that language. For instance, namespaces will be presented as packages,
-# qualified scopes will look different, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_JAVA = NO
-
-# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources. Doxygen will then generate output that is tailored for Fortran.
-# The default value is: NO.
-
-OPTIMIZE_FOR_FORTRAN = NO
-
-# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for VHDL.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_VHDL = NO
-
-# Doxygen selects the parser to use depending on the extension of the files it
-# parses. With this tag you can assign which parser to use for a given
-# extension. Doxygen has a built-in mapping, but you can override or extend it
-# using this tag. The format is ext=language, where ext is a file extension, and
-# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
-# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
-# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
-# Fortran. In the later case the parser tries to guess whether the code is fixed
-# or free formatted code, this is the default for Fortran type files), VHDL. For
-# instance to make doxygen treat .inc files as Fortran files (default is PHP),
-# and .f files as C (default is Fortran), use: inc=Fortran f=C.
-#
-# Note: For files without extension you can use no_extension as a placeholder.
-#
-# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
-# the files are not read by doxygen.
-
-EXTENSION_MAPPING =
-
-# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
-# according to the Markdown format, which allows for more readable
-# documentation. See http://daringfireball.net/projects/markdown/ for details.
-# The output of markdown processing is further processed by doxygen, so you can
-# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
-# case of backward compatibilities issues.
-# The default value is: YES.
-
-MARKDOWN_SUPPORT = NO
-
-# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
-# to that level are automatically included in the table of contents, even if
-# they do not have an id attribute.
-# Note: This feature currently applies only to Markdown headings.
-# Minimum value: 0, maximum value: 99, default value: 0.
-# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
-
-TOC_INCLUDE_HEADINGS = 0
-
-# When enabled doxygen tries to link words that correspond to documented
-# classes, or namespaces to their corresponding documentation. Such a link can
-# be prevented in individual cases by putting a % sign in front of the word or
-# globally by setting AUTOLINK_SUPPORT to NO.
-# The default value is: YES.
-
-AUTOLINK_SUPPORT = YES
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should set this
-# tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string);
-# versus func(std::string) {}). This also make the inheritance and collaboration
-# diagrams that involve STL classes more complete and accurate.
-# The default value is: NO.
-
-BUILTIN_STL_SUPPORT = YES
-
-# If you use Microsoft's C++/CLI language, you should set this option to YES to
-# enable parsing support.
-# The default value is: NO.
-
-CPP_CLI_SUPPORT = NO
-
-# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
-# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
-# will parse them like normal C++ but will assume all classes use public instead
-# of private inheritance when no explicit protection keyword is present.
-# The default value is: NO.
-
-SIP_SUPPORT = NO
-
-# For Microsoft's IDL there are propget and propput attributes to indicate
-# getter and setter methods for a property. Setting this option to YES will make
-# doxygen to replace the get and set methods by a property in the documentation.
-# This will only work if the methods are indeed getting or setting a simple
-# type. If this is not the case, or you want to show the methods anyway, you
-# should set this option to NO.
-# The default value is: YES.
-
-IDL_PROPERTY_SUPPORT = YES
-
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
-# all members of a group must be documented explicitly.
-# The default value is: NO.
-
-DISTRIBUTE_GROUP_DOC = NO
-
-# If one adds a struct or class to a group and this option is enabled, then also
-# any nested class or struct is added to the same group. By default this option
-# is disabled and one has to add nested compounds explicitly via \ingroup.
-# The default value is: NO.
-
-GROUP_NESTED_COMPOUNDS = YES
-
-# Set the SUBGROUPING tag to YES to allow class member groups of the same type
-# (for instance a group of public functions) to be put as a subgroup of that
-# type (e.g. under the Public Functions section). Set it to NO to prevent
-# subgrouping. Alternatively, this can be done per class using the
-# \nosubgrouping command.
-# The default value is: YES.
-
-SUBGROUPING = YES
-
-# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
-# are shown inside the group in which they are included (e.g. using \ingroup)
-# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
-# and RTF).
-#
-# Note that this feature does not work in combination with
-# SEPARATE_MEMBER_PAGES.
-# The default value is: NO.
-
-INLINE_GROUPED_CLASSES = YES
-
-# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
-# with only public data fields or simple typedef fields will be shown inline in
-# the documentation of the scope in which they are defined (i.e. file,
-# namespace, or group documentation), provided this scope is documented. If set
-# to NO, structs, classes, and unions are shown on a separate page (for HTML and
-# Man pages) or section (for LaTeX and RTF).
-# The default value is: NO.
-
-INLINE_SIMPLE_STRUCTS = NO
-
-# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
-# enum is documented as struct, union, or enum with the name of the typedef. So
-# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
-# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically be
-# useful for C code in case the coding convention dictates that all compound
-# types are typedef'ed and only the typedef is referenced, never the tag name.
-# The default value is: NO.
-
-TYPEDEF_HIDES_STRUCT = YES
-
-# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
-# cache is used to resolve symbols given their name and scope. Since this can be
-# an expensive process and often the same symbol appears multiple times in the
-# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
-# doxygen will become slower. If the cache is too large, memory is wasted. The
-# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
-# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
-# symbols. At the end of a run doxygen will report the cache usage and suggest
-# the optimal cache size from a speed point of view.
-# Minimum value: 0, maximum value: 9, default value: 0.
-
-LOOKUP_CACHE_SIZE = 1
-
-#---------------------------------------------------------------------------
-# Build related configuration options
-#---------------------------------------------------------------------------
-
-# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
-# documentation are documented, even if no documentation was available. Private
-# class members and static file members will be hidden unless the
-# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
-# Note: This will also disable the warnings about undocumented members that are
-# normally produced when WARNINGS is set to YES.
-# The default value is: NO.
-
-EXTRACT_ALL = ALL
-
-# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
-# be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PRIVATE = YES
-
-# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
-# scope will be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PACKAGE = YES
-
-# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
-# included in the documentation.
-# The default value is: NO.
-
-EXTRACT_STATIC = YES
-
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
-# locally in source files will be included in the documentation. If set to NO,
-# only classes defined in header files are included. Does not have any effect
-# for Java sources.
-# The default value is: YES.
-
-EXTRACT_LOCAL_CLASSES = YES
-
-# This flag is only useful for Objective-C code. If set to YES, local methods,
-# which are defined in the implementation section but not in the interface are
-# included in the documentation. If set to NO, only methods in the interface are
-# included.
-# The default value is: NO.
-
-EXTRACT_LOCAL_METHODS = NO
-
-# If this flag is set to YES, the members of anonymous namespaces will be
-# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base name of
-# the file that contains the anonymous namespace. By default anonymous namespace
-# are hidden.
-# The default value is: NO.
-
-EXTRACT_ANON_NSPACES = NO
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
-# undocumented members inside documented classes or files. If set to NO these
-# members will be included in the various overviews, but no documentation
-# section is generated. This option has no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_MEMBERS = YES
-
-# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy. If set
-# to NO, these classes will be included in the various overviews. This option
-# has no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_CLASSES = YES
-
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
-# (class|struct|union) declarations. If set to NO, these declarations will be
-# included in the documentation.
-# The default value is: NO.
-
-HIDE_FRIEND_COMPOUNDS = NO
-
-# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
-# documentation blocks found inside the body of a function. If set to NO, these
-# blocks will be appended to the function's detailed documentation block.
-# The default value is: NO.
-
-HIDE_IN_BODY_DOCS = NO
-
-# The INTERNAL_DOCS tag determines if documentation that is typed after a
-# \internal command is included. If the tag is set to NO then the documentation
-# will be excluded. Set it to YES to include the internal documentation.
-# The default value is: NO.
-
-INTERNAL_DOCS = YES
-
-# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
-# names in lower-case letters. If set to YES, upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
-# The default value is: system dependent.
-
-CASE_SENSE_NAMES = NO
-
-# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
-# their full class and namespace scopes in the documentation. If set to YES, the
-# scope will be hidden.
-# The default value is: NO.
-
-HIDE_SCOPE_NAMES = NO
-
-# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
-# append additional text to a page's title, such as Class Reference. If set to
-# YES the compound reference will be hidden.
-# The default value is: NO.
-
-HIDE_COMPOUND_REFERENCE= YES
-
-# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
-# the files that are included by a file in the documentation of that file.
-# The default value is: YES.
-
-SHOW_INCLUDE_FILES = NO
-
-# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
-# grouped member an include statement to the documentation, telling the reader
-# which file to include in order to use the member.
-# The default value is: NO.
-
-SHOW_GROUPED_MEMB_INC = YES
-
-# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
-# files with double quotes in the documentation rather than with sharp brackets.
-# The default value is: NO.
-
-FORCE_LOCAL_INCLUDES = NO
-
-# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
-# documentation for inline members.
-# The default value is: YES.
-
-INLINE_INFO = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
-# (detailed) documentation of file and class members alphabetically by member
-# name. If set to NO, the members will appear in declaration order.
-# The default value is: YES.
-
-SORT_MEMBER_DOCS = YES
-
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
-# descriptions of file, namespace and class members alphabetically by member
-# name. If set to NO, the members will appear in declaration order. Note that
-# this will also influence the order of the classes in the class list.
-# The default value is: NO.
-
-SORT_BRIEF_DOCS = NO
-
-# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
-# (brief and detailed) documentation of class members so that constructors and
-# destructors are listed first. If set to NO the constructors will appear in the
-# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
-# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
-# member documentation.
-# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
-# detailed member documentation.
-# The default value is: NO.
-
-SORT_MEMBERS_CTORS_1ST = YES
-
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
-# of group names into alphabetical order. If set to NO the group names will
-# appear in their defined order.
-# The default value is: NO.
-
-SORT_GROUP_NAMES = NO
-
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
-# fully-qualified names, including namespaces. If set to NO, the class list will
-# be sorted only by class name, not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the alphabetical
-# list.
-# The default value is: NO.
-
-SORT_BY_SCOPE_NAME = YES
-
-# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
-# type resolution of all parameters of a function it will reject a match between
-# the prototype and the implementation of a member function even if there is
-# only one candidate or it is obvious which candidate to choose by doing a
-# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
-# accept a match between prototype and implementation in such cases.
-# The default value is: NO.
-
-STRICT_PROTO_MATCHING = YES
-
-# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
-# list. This list is created by putting \todo commands in the documentation.
-# The default value is: YES.
-
-GENERATE_TODOLIST = YES
-
-# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
-# list. This list is created by putting \test commands in the documentation.
-# The default value is: YES.
-
-GENERATE_TESTLIST = YES
-
-# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
-# list. This list is created by putting \bug commands in the documentation.
-# The default value is: YES.
-
-GENERATE_BUGLIST = YES
-
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
-# the deprecated list. This list is created by putting \deprecated commands in
-# the documentation.
-# The default value is: YES.
-
-GENERATE_DEPRECATEDLIST= YES
-
-# The ENABLED_SECTIONS tag can be used to enable conditional documentation
-# sections, marked by \if <section_label> ... \endif and \cond <section_label>
-# ... \endcond blocks.
-
-ENABLED_SECTIONS =
-
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
-# initial value of a variable or macro / define can have for it to appear in the
-# documentation. If the initializer consists of more lines than specified here
-# it will be hidden. Use a value of 0 to hide initializers completely. The
-# appearance of the value of individual variables and macros / defines can be
-# controlled using \showinitializer or \hideinitializer command in the
-# documentation regardless of this setting.
-# Minimum value: 0, maximum value: 10000, default value: 30.
-
-MAX_INITIALIZER_LINES = 30
-
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
-# the bottom of the documentation of classes and structs. If set to YES, the
-# list will mention the files that were used to generate the documentation.
-# The default value is: YES.
-
-SHOW_USED_FILES = NO
-
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
-# will remove the Files entry from the Quick Index and from the Folder Tree View
-# (if specified).
-# The default value is: YES.
-
-SHOW_FILES = YES
-
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
-# page. This will remove the Namespaces entry from the Quick Index and from the
-# Folder Tree View (if specified).
-# The default value is: YES.
-
-SHOW_NAMESPACES = YES
-
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from
-# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command command input-file, where command is the value of the
-# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
-# by doxygen. Whatever the program writes to standard output is used as the file
-# version. For an example see the documentation.
-
-FILE_VERSION_FILTER =
-
-# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
-# by doxygen. The layout file controls the global structure of the generated
-# output files in an output format independent way. To create the layout file
-# that represents doxygen's defaults, run doxygen with the -l option. You can
-# optionally specify a file name after the option, if omitted DoxygenLayout.xml
-# will be used as the name of the layout file.
-#
-# Note that if you run doxygen from a directory containing a file called
-# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
-# tag is left empty.
-
-LAYOUT_FILE =
-
-# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
-# the reference definitions. This must be a list of .bib files. The .bib
-# extension is automatically appended if omitted. This requires the bibtex tool
-# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
-# For LaTeX the style of the bibliography can be controlled using
-# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
-# search path. See also \cite for info how to create references.
-
-CITE_BIB_FILES =
-
-#---------------------------------------------------------------------------
-# Configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated to
-# standard output by doxygen. If QUIET is set to YES this implies that the
-# messages are off.
-# The default value is: NO.
-
-QUIET = NO
-
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
-# this implies that the warnings are on.
-#
-# Tip: Turn warnings on while writing the documentation.
-# The default value is: YES.
-
-WARNINGS = YES
-
-# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
-# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
-# will automatically be disabled.
-# The default value is: YES.
-
-WARN_IF_UNDOCUMENTED = NO
-
-# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some parameters
-# in a documented function, or documenting parameters that don't exist or using
-# markup commands wrongly.
-# The default value is: YES.
-
-WARN_IF_DOC_ERROR = YES
-
-# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
-# are documented, but have no documentation for their parameters or return
-# value. If set to NO, doxygen will only warn about wrong or incomplete
-# parameter documentation, but not about the absence of documentation.
-# The default value is: NO.
-
-WARN_NO_PARAMDOC = NO
-
-# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
-# a warning is encountered.
-# The default value is: NO.
-
-WARN_AS_ERROR = NO
-
-# The WARN_FORMAT tag determines the format of the warning messages that doxygen
-# can produce. The string should contain the $file, $line, and $text tags, which
-# will be replaced by the file and line number from which the warning originated
-# and the warning text. Optionally the format may contain $version, which will
-# be replaced by the version of the file (if it could be obtained via
-# FILE_VERSION_FILTER)
-# The default value is: $file:$line: $text.
-
-WARN_FORMAT = "$file:$line: $text"
-
-# The WARN_LOGFILE tag can be used to specify a file to which warning and error
-# messages should be written. If left blank the output is written to standard
-# error (stderr).
-
-WARN_LOGFILE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag is used to specify the files and/or directories that contain
-# documented source files. You may enter file names like myfile.cpp or
-# directories like /usr/src/myproject. Separate the files or directories with
-# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
-# Note: If this tag is empty the current directory is searched.
-
-INPUT = ../../opentrack
-
-# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
-# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
-# documentation (see: http://www.gnu.org/software/libiconv) for the list of
-# possible encodings.
-# The default value is: UTF-8.
-
-INPUT_ENCODING = UTF-8
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
-# *.h) to filter out the source-files in the directories.
-#
-# Note that for custom extensions or not directly supported extensions you also
-# need to set EXTENSION_MAPPING for the extension otherwise the files are not
-# read by doxygen.
-#
-# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
-# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
-# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
-# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
-# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
-
-FILE_PATTERNS = *.c \
- *.cc \
- *.cxx \
- *.cpp \
- *.c++ \
- *.java \
- *.ii \
- *.ixx \
- *.ipp \
- *.i++ \
- *.inl \
- *.idl \
- *.ddl \
- *.odl \
- *.h \
- *.hh \
- *.hxx \
- *.hpp \
- *.h++ \
- *.cs \
- *.d \
- *.php \
- *.php4 \
- *.php5 \
- *.phtml \
- *.inc \
- *.m \
- *.mm \
- *.dox \
- *.f90 \
- *.f95 \
- *.f03 \
- *.f08 \
- *.f \
- *.for \
- *.tcl \
- *.vhd \
- *.vhdl \
- *.ucf \
- *.qsf
-
-# The RECURSIVE tag can be used to specify whether or not subdirectories should
-# be searched for input files as well.
-# The default value is: NO.
-
-RECURSIVE = YES
-
-# The EXCLUDE tag can be used to specify files and/or directories that should be
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-#
-# Note that relative paths are relative to the directory from which doxygen is
-# run.
-
-EXCLUDE =
-
-# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
-# directories that are symbolic links (a Unix file system feature) are excluded
-# from the input.
-# The default value is: NO.
-
-EXCLUDE_SYMLINKS = NO
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories.
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories for example use the pattern */test/*
-
-EXCLUDE_PATTERNS = */contrib*/* */build*/* */tracker-aruco/include/*
-
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
-# (namespaces, classes, functions, etc.) that should be excluded from the
-# output. The symbol name can be a fully qualified name, a word, or if the
-# wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories use the pattern */test/*
-
-EXCLUDE_SYMBOLS = std
-
-# The EXAMPLE_PATH tag can be used to specify one or more files or directories
-# that contain example code fragments that are included (see the \include
-# command).
-
-EXAMPLE_PATH =
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
-# *.h) to filter out the source-files in the directories. If left blank all
-# files are included.
-
-EXAMPLE_PATTERNS = *
-
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude commands
-# irrespective of the value of the RECURSIVE tag.
-# The default value is: NO.
-
-EXAMPLE_RECURSIVE = NO
-
-# The IMAGE_PATH tag can be used to specify one or more files or directories
-# that contain images that are to be included in the documentation (see the
-# \image command).
-
-IMAGE_PATH =
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command:
-#
-# <filter> <input-file>
-#
-# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
-# name of an input file. Doxygen will then use the output that the filter
-# program writes to standard output. If FILTER_PATTERNS is specified, this tag
-# will be ignored.
-#
-# Note that the filter must not add or remove lines; it is applied before the
-# code is scanned, but not when the output code is generated. If lines are added
-# or removed, the anchors will not be placed correctly.
-#
-# Note that for custom extensions or not directly supported extensions you also
-# need to set EXTENSION_MAPPING for the extension otherwise the files are not
-# properly processed by doxygen.
-
-INPUT_FILTER =
-
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis. Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match. The filters are a list of the form: pattern=filter
-# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
-# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
-# patterns match the file name, INPUT_FILTER is applied.
-#
-# Note that for custom extensions or not directly supported extensions you also
-# need to set EXTENSION_MAPPING for the extension otherwise the files are not
-# properly processed by doxygen.
-
-FILTER_PATTERNS =
-
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER) will also be used to filter the input files that are used for
-# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
-# The default value is: NO.
-
-FILTER_SOURCE_FILES = NO
-
-# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
-# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
-# it is also possible to disable source filtering for a specific pattern using
-# *.ext= (so without naming a filter).
-# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
-
-FILTER_SOURCE_PATTERNS =
-
-# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
-# is part of the input, its contents will be placed on the main page
-# (index.html). This can be useful if you have a project on for instance GitHub
-# and want to reuse the introduction page also for the doxygen output.
-
-USE_MDFILE_AS_MAINPAGE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to source browsing
-#---------------------------------------------------------------------------
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
-# generated. Documented entities will be cross-referenced with these sources.
-#
-# Note: To get rid of all source code in the generated output, make sure that
-# also VERBATIM_HEADERS is set to NO.
-# The default value is: NO.
-
-SOURCE_BROWSER = YES
-
-# Setting the INLINE_SOURCES tag to YES will include the body of functions,
-# classes and enums directly into the documentation.
-# The default value is: NO.
-
-INLINE_SOURCES = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
-# special comment blocks from generated source code fragments. Normal C, C++ and
-# Fortran comments will always remain visible.
-# The default value is: YES.
-
-STRIP_CODE_COMMENTS = YES
-
-# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
-# function all documented functions referencing it will be listed.
-# The default value is: NO.
-
-REFERENCED_BY_RELATION = YES
-
-# If the REFERENCES_RELATION tag is set to YES then for each documented function
-# all documented entities called/used by that function will be listed.
-# The default value is: NO.
-
-REFERENCES_RELATION = YES
-
-# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
-# to YES then the hyperlinks from functions in REFERENCES_RELATION and
-# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
-# link to the documentation.
-# The default value is: YES.
-
-REFERENCES_LINK_SOURCE = YES
-
-# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
-# source code will show a tooltip with additional information such as prototype,
-# brief description and links to the definition and documentation. Since this
-# will make the HTML file larger and loading of large files a bit slower, you
-# can opt to disable this feature.
-# The default value is: YES.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-SOURCE_TOOLTIPS = YES
-
-# If the USE_HTAGS tag is set to YES then the references to source code will
-# point to the HTML generated by the htags(1) tool instead of doxygen built-in
-# source browser. The htags tool is part of GNU's global source tagging system
-# (see http://www.gnu.org/software/global/global.html). You will need version
-# 4.8.6 or higher.
-#
-# To use it do the following:
-# - Install the latest version of global
-# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
-# - Make sure the INPUT points to the root of the source tree
-# - Run doxygen as normal
-#
-# Doxygen will invoke htags (and that will in turn invoke gtags), so these
-# tools must be available from the command line (i.e. in the search path).
-#
-# The result: instead of the source browser generated by doxygen, the links to
-# source code will now point to the output of htags.
-# The default value is: NO.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-USE_HTAGS = NO
-
-# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
-# verbatim copy of the header file for each class for which an include is
-# specified. Set to NO to disable this.
-# See also: Section \class.
-# The default value is: YES.
-
-VERBATIM_HEADERS = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
-# compounds will be generated. Enable this if the project contains a lot of
-# classes, structs, unions or interfaces.
-# The default value is: YES.
-
-ALPHABETICAL_INDEX = YES
-
-# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
-# which the alphabetical index list will be split.
-# Minimum value: 1, maximum value: 20, default value: 5.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-COLS_IN_ALPHA_INDEX = 5
-
-# In case all classes in a project start with a common prefix, all classes will
-# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
-# can be used to specify a prefix (or a list of prefixes) that should be ignored
-# while generating the index headers.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-IGNORE_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
-# The default value is: YES.
-
-GENERATE_HTML = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_OUTPUT = html
-
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
-# generated HTML page (for example: .htm, .php, .asp).
-# The default value is: .html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FILE_EXTENSION = .html
-
-# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
-# each generated HTML page. If the tag is left blank doxygen will generate a
-# standard header.
-#
-# To get valid HTML the header file that includes any scripts and style sheets
-# that doxygen needs, which is dependent on the configuration options used (e.g.
-# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
-# default header using
-# doxygen -w html new_header.html new_footer.html new_stylesheet.css
-# YourConfigFile
-# and then modify the file new_header.html. See also section "Doxygen usage"
-# for information on how to generate the default header that doxygen normally
-# uses.
-# Note: The header is subject to change so you typically have to regenerate the
-# default header when upgrading to a newer version of doxygen. For a description
-# of the possible markers and block names see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_HEADER =
-
-# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
-# generated HTML page. If the tag is left blank doxygen will generate a standard
-# footer. See HTML_HEADER for more information on how to generate a default
-# footer and what special commands can be used inside the footer. See also
-# section "Doxygen usage" for information on how to generate the default footer
-# that doxygen normally uses.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FOOTER =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
-# sheet that is used by each HTML page. It can be used to fine-tune the look of
-# the HTML output. If left blank doxygen will generate a default style sheet.
-# See also section "Doxygen usage" for information on how to generate the style
-# sheet that doxygen normally uses.
-# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
-# it is more robust and this tag (HTML_STYLESHEET) will in the future become
-# obsolete.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_STYLESHEET =
-
-# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
-# cascading style sheets that are included after the standard style sheets
-# created by doxygen. Using this option one can overrule certain style aspects.
-# This is preferred over using HTML_STYLESHEET since it does not replace the
-# standard style sheet and is therefore more robust against future updates.
-# Doxygen will copy the style sheet files to the output directory.
-# Note: The order of the extra style sheet files is of importance (e.g. the last
-# style sheet in the list overrules the setting of the previous ones in the
-# list). For an example see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_STYLESHEET =
-
-# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the HTML output directory. Note
-# that these files will be copied to the base HTML output directory. Use the
-# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
-# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
-# files will be copied as-is; there are no commands or markers available.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_FILES =
-
-# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
-# will adjust the colors in the style sheet and background images according to
-# this color. Hue is specified as an angle on a colorwheel, see
-# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
-# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
-# purple, and 360 is red again.
-# Minimum value: 0, maximum value: 359, default value: 220.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_HUE = 220
-
-# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
-# in the HTML output. For a value of 0 the output will use grayscales only. A
-# value of 255 will produce the most vivid colors.
-# Minimum value: 0, maximum value: 255, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_SAT = 100
-
-# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
-# luminance component of the colors in the HTML output. Values below 100
-# gradually make the output lighter, whereas values above 100 make the output
-# darker. The value divided by 100 is the actual gamma applied, so 80 represents
-# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
-# change the gamma.
-# Minimum value: 40, maximum value: 240, default value: 80.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_GAMMA = 80
-
-# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting this
-# to YES can help to show when doxygen was last run and thus if the
-# documentation is up to date.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_TIMESTAMP = YES
-
-# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
-# documentation will contain sections that can be hidden and shown after the
-# page has loaded.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_DYNAMIC_SECTIONS = NO
-
-# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
-# shown in the various tree structured indices initially; the user can expand
-# and collapse entries dynamically later on. Doxygen will expand the tree to
-# such a level that at most the specified number of entries are visible (unless
-# a fully collapsed tree already exceeds this amount). So setting the number of
-# entries 1 will produce a full collapsed tree by default. 0 is a special value
-# representing an infinite number of entries and will result in a full expanded
-# tree by default.
-# Minimum value: 0, maximum value: 9999, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_INDEX_NUM_ENTRIES = 100
-
-# If the GENERATE_DOCSET tag is set to YES, additional index files will be
-# generated that can be used as input for Apple's Xcode 3 integrated development
-# environment (see: http://developer.apple.com/tools/xcode/), introduced with
-# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
-# Makefile in the HTML output directory. Running make will produce the docset in
-# that directory and running make install will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
-# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
-# for more information.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_DOCSET = NO
-
-# This tag determines the name of the docset feed. A documentation feed provides
-# an umbrella under which multiple documentation sets from a single provider
-# (such as a company or product suite) can be grouped.
-# The default value is: Doxygen generated docs.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_FEEDNAME = "Doxygen generated docs"
-
-# This tag specifies a string that should uniquely identify the documentation
-# set bundle. This should be a reverse domain-name style string, e.g.
-# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_BUNDLE_ID = com.github.opentrack
-
-# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
-# the documentation publisher. This should be a reverse domain-name style
-# string, e.g. com.mycompany.MyDocSet.documentation.
-# The default value is: org.doxygen.Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_ID = com.github.opentrack
-
-# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
-# The default value is: Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_NAME = opentrack
-
-# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
-# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
-# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
-# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
-# Windows.
-#
-# The HTML Help Workshop contains a compiler that can convert all HTML output
-# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
-# files are now used as the Windows 98 help format, and will replace the old
-# Windows help format (.hlp) on all Windows platforms in the future. Compressed
-# HTML files also contain an index, a table of contents, and you can search for
-# words in the documentation. The HTML workshop also contains a viewer for
-# compressed HTML files.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_HTMLHELP = NO
-
-# The CHM_FILE tag can be used to specify the file name of the resulting .chm
-# file. You can add a path in front of the file if the result should not be
-# written to the html output directory.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_FILE =
-
-# The HHC_LOCATION tag can be used to specify the location (absolute path
-# including file name) of the HTML help compiler (hhc.exe). If non-empty,
-# doxygen will try to run the HTML help compiler on the generated index.hhp.
-# The file has to be specified with full path.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-HHC_LOCATION =
-
-# The GENERATE_CHI flag controls if a separate .chi index file is generated
-# (YES) or that it should be included in the master .chm file (NO).
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-GENERATE_CHI = NO
-
-# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
-# and project file content.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_INDEX_ENCODING =
-
-# The BINARY_TOC flag controls whether a binary table of contents is generated
-# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
-# enables the Previous and Next buttons.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-BINARY_TOC = NO
-
-# The TOC_EXPAND flag can be set to YES to add extra items for group members to
-# the table of contents of the HTML help documentation and to the tree view.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-TOC_EXPAND = NO
-
-# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
-# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
-# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
-# (.qch) of the generated HTML documentation.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_QHP = NO
-
-# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
-# the file name of the resulting .qch file. The path specified is relative to
-# the HTML output folder.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QCH_FILE =
-
-# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
-# Project output. For more information please see Qt Help Project / Namespace
-# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_NAMESPACE = org.doxygen.Project
-
-# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
-# Help Project output. For more information please see Qt Help Project / Virtual
-# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
-# folders).
-# The default value is: doc.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_VIRTUAL_FOLDER = doc
-
-# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
-# filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
-# filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_NAME =
-
-# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
-# custom filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
-# filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_ATTRS =
-
-# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
-# project's filter section matches. Qt Help Project / Filter Attributes (see:
-# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_SECT_FILTER_ATTRS =
-
-# The QHG_LOCATION tag can be used to specify the location of Qt's
-# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
-# generated .qhp file.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHG_LOCATION =
-
-# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
-# generated, together with the HTML files, they form an Eclipse help plugin. To
-# install this plugin and make it available under the help contents menu in
-# Eclipse, the contents of the directory containing the HTML and XML files needs
-# to be copied into the plugins directory of eclipse. The name of the directory
-# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
-# After copying Eclipse needs to be restarted before the help appears.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_ECLIPSEHELP = NO
-
-# A unique identifier for the Eclipse help plugin. When installing the plugin
-# the directory name containing the HTML and XML files should also have this
-# name. Each documentation set should have its own identifier.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
-
-ECLIPSE_DOC_ID = org.doxygen.Project
-
-# If you want full control over the layout of the generated HTML pages it might
-# be necessary to disable the index and replace it with your own. The
-# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
-# of each HTML page. A value of NO enables the index and the value YES disables
-# it. Since the tabs in the index contain the same information as the navigation
-# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-DISABLE_INDEX = NO
-
-# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information. If the tag
-# value is set to YES, a side panel will be generated containing a tree-like
-# index structure (just like the one that is generated for HTML Help). For this
-# to work a browser that supports JavaScript, DHTML, CSS and frames is required
-# (i.e. any modern browser). Windows users are probably better off using the
-# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
-# further fine-tune the look of the index. As an example, the default style
-# sheet generated by doxygen has an example that shows how to put an image at
-# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
-# the same information as the tab index, you could consider setting
-# DISABLE_INDEX to YES when enabling this option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_TREEVIEW = YES
-
-# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
-# doxygen will group on one line in the generated HTML documentation.
-#
-# Note that a value of 0 will completely suppress the enum values from appearing
-# in the overview section.
-# Minimum value: 0, maximum value: 20, default value: 4.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-ENUM_VALUES_PER_LINE = 4
-
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
-# to set the initial width (in pixels) of the frame in which the tree is shown.
-# Minimum value: 0, maximum value: 1500, default value: 250.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-TREEVIEW_WIDTH = 250
-
-# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
-# external symbols imported via tag files in a separate window.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-EXT_LINKS_IN_WINDOW = NO
-
-# Use this tag to change the font size of LaTeX formulas included as images in
-# the HTML documentation. When you change the font size after a successful
-# doxygen run you need to manually remove any form_*.png images from the HTML
-# output directory to force them to be regenerated.
-# Minimum value: 8, maximum value: 50, default value: 10.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_FONTSIZE = 10
-
-# Use the FORMULA_TRANPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are not
-# supported properly for IE 6.0, but are supported on all modern browsers.
-#
-# Note that when changing this option you need to delete any form_*.png files in
-# the HTML output directory before the changes have effect.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_TRANSPARENT = YES
-
-# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
-# http://www.mathjax.org) which uses client side Javascript for the rendering
-# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
-# installed or if you want to formulas look prettier in the HTML output. When
-# enabled you may also need to install MathJax separately and configure the path
-# to it using the MATHJAX_RELPATH option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-USE_MATHJAX = NO
-
-# When MathJax is enabled you can set the default output format to be used for
-# the MathJax output. See the MathJax site (see:
-# http://docs.mathjax.org/en/latest/output.html) for more details.
-# Possible values are: HTML-CSS (which is slower, but has the best
-# compatibility), NativeMML (i.e. MathML) and SVG.
-# The default value is: HTML-CSS.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_FORMAT = HTML-CSS
-
-# When MathJax is enabled you need to specify the location relative to the HTML
-# output directory using the MATHJAX_RELPATH option. The destination directory
-# should contain the MathJax.js script. For instance, if the mathjax directory
-# is located at the same level as the HTML output directory, then
-# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
-# Content Delivery Network so you can quickly see the result without installing
-# MathJax. However, it is strongly recommended to install a local copy of
-# MathJax from http://www.mathjax.org before deployment.
-# The default value is: http://cdn.mathjax.org/mathjax/latest.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
-
-# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
-# extension names that should be enabled during MathJax rendering. For example
-# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_EXTENSIONS =
-
-# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
-# of code that will be used on startup of the MathJax code. See the MathJax site
-# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
-# example see the documentation.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_CODEFILE =
-
-# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
-# the HTML output. The underlying search engine uses javascript and DHTML and
-# should work on any modern browser. Note that when using HTML help
-# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
-# there is already a search function so this one should typically be disabled.
-# For large projects the javascript based search engine can be slow, then
-# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
-# search using the keyboard; to jump to the search box use <access key> + S
-# (what the <access key> is depends on the OS and browser, but it is typically
-# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
-# key> to jump into the search results window, the results can be navigated
-# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
-# the search. The filter options can be selected when the cursor is inside the
-# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
-# to select a filter and <Enter> or <escape> to activate or cancel the filter
-# option.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-SEARCHENGINE = YES
-
-# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a web server instead of a web client using Javascript. There
-# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
-# setting. When disabled, doxygen will generate a PHP script for searching and
-# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
-# and searching needs to be provided by external tools. See the section
-# "External Indexing and Searching" for details.
-# The default value is: NO.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SERVER_BASED_SEARCH = NO
-
-# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
-# script for searching. Instead the search results are written to an XML file
-# which needs to be processed by an external indexer. Doxygen will invoke an
-# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
-# search results.
-#
-# Doxygen ships with an example indexer (doxyindexer) and search engine
-# (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: http://xapian.org/).
-#
-# See the section "External Indexing and Searching" for details.
-# The default value is: NO.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTERNAL_SEARCH = NO
-
-# The SEARCHENGINE_URL should point to a search engine hosted by a web server
-# which will return the search results when EXTERNAL_SEARCH is enabled.
-#
-# Doxygen ships with an example indexer (doxyindexer) and search engine
-# (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: http://xapian.org/). See the section "External Indexing and
-# Searching" for details.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SEARCHENGINE_URL =
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
-# search data is written to a file for indexing by an external tool. With the
-# SEARCHDATA_FILE tag the name of this file can be specified.
-# The default file is: searchdata.xml.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SEARCHDATA_FILE = searchdata.xml
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
-# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
-# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
-# projects and redirect the results back to the right project.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTERNAL_SEARCH_ID =
-
-# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
-# projects other than the one defined by this configuration file, but that are
-# all added to the same external search index. Each project needs to have a
-# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
-# to a relative location where the documentation can be found. The format is:
-# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTRA_SEARCH_MAPPINGS =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
-# The default value is: YES.
-
-GENERATE_LATEX = YES
-
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: latex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_OUTPUT = latex
-
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked.
-#
-# Note that when enabling USE_PDFLATEX this option is only used for generating
-# bitmaps for formulas in the HTML output, but not in the Makefile that is
-# written to the output directory.
-# The default file is: latex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_CMD_NAME = latex
-
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
-# index for LaTeX.
-# The default file is: makeindex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-MAKEINDEX_CMD_NAME = makeindex
-
-# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
-# documents. This may be useful for small projects and may help to save some
-# trees in general.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-COMPACT_LATEX = NO
-
-# The PAPER_TYPE tag can be used to set the paper type that is used by the
-# printer.
-# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
-# 14 inches) and executive (7.25 x 10.5 inches).
-# The default value is: a4.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-PAPER_TYPE = a4
-
-# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
-# that should be included in the LaTeX output. The package can be specified just
-# by its name or with the correct syntax as to be used with the LaTeX
-# \usepackage command. To get the times font for instance you can specify :
-# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
-# To use the option intlimits with the amsmath package you can specify:
-# EXTRA_PACKAGES=[intlimits]{amsmath}
-# If left blank no extra packages will be included.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-EXTRA_PACKAGES =
-
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
-# generated LaTeX document. The header should contain everything until the first
-# chapter. If it is left blank doxygen will generate a standard header. See
-# section "Doxygen usage" for information on how to let doxygen write the
-# default header to a separate file.
-#
-# Note: Only use a user-defined header if you know what you are doing! The
-# following commands have a special meaning inside the header: $title,
-# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
-# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
-# string, for the replacement values of the other commands the user is referred
-# to HTML_HEADER.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_HEADER =
-
-# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
-# generated LaTeX document. The footer should contain everything after the last
-# chapter. If it is left blank doxygen will generate a standard footer. See
-# LATEX_HEADER for more information on how to generate a default footer and what
-# special commands can be used inside the footer.
-#
-# Note: Only use a user-defined footer if you know what you are doing!
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_FOOTER =
-
-# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
-# LaTeX style sheets that are included after the standard style sheets created
-# by doxygen. Using this option one can overrule certain style aspects. Doxygen
-# will copy the style sheet files to the output directory.
-# Note: The order of the extra style sheet files is of importance (e.g. the last
-# style sheet in the list overrules the setting of the previous ones in the
-# list).
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_EXTRA_STYLESHEET =
-
-# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the LATEX_OUTPUT output
-# directory. Note that the files will be copied as-is; there are no commands or
-# markers available.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_EXTRA_FILES =
-
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
-# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
-# contain links (just like the HTML output) instead of page references. This
-# makes the output suitable for online browsing using a PDF viewer.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-PDF_HYPERLINKS = YES
-
-# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
-# the PDF file directly from the LaTeX files. Set this option to YES, to get a
-# higher quality PDF documentation.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-USE_PDFLATEX = YES
-
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
-# command to the generated LaTeX files. This will instruct LaTeX to keep running
-# if errors occur, instead of asking the user for help. This option is also used
-# when generating formulas in HTML.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_BATCHMODE = NO
-
-# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
-# index chapters (such as File Index, Compound Index, etc.) in the output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_HIDE_INDICES = NO
-
-# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
-# code with syntax highlighting in the LaTeX output.
-#
-# Note that which sources are shown also depends on other settings such as
-# SOURCE_BROWSER.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_SOURCE_CODE = NO
-
-# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
-# bibliography, e.g. plainnat, or ieeetr. See
-# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
-# The default value is: plain.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_BIB_STYLE = plain
-
-# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
-# page will contain the date and time when the page was generated. Setting this
-# to NO can help when comparing the output of multiple runs.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_TIMESTAMP = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the RTF output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
-# RTF output is optimized for Word 97 and may not look too pretty with other RTF
-# readers/editors.
-# The default value is: NO.
-
-GENERATE_RTF = NO
-
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: rtf.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_OUTPUT = rtf
-
-# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
-# documents. This may be useful for small projects and may help to save some
-# trees in general.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-COMPACT_RTF = NO
-
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
-# contain hyperlink fields. The RTF file will contain links (just like the HTML
-# output) instead of page references. This makes the output suitable for online
-# browsing using Word or some other Word compatible readers that support those
-# fields.
-#
-# Note: WordPad (write) and others do not support links.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_HYPERLINKS = NO
-
-# Load stylesheet definitions from file. Syntax is similar to doxygen's config
-# file, i.e. a series of assignments. You only have to provide replacements,
-# missing definitions are set to their default value.
-#
-# See also section "Doxygen usage" for information on how to generate the
-# default style sheet that doxygen normally uses.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_STYLESHEET_FILE =
-
-# Set optional variables used in the generation of an RTF document. Syntax is
-# similar to doxygen's config file. A template extensions file can be generated
-# using doxygen -e rtf extensionFile.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_EXTENSIONS_FILE =
-
-# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
-# with syntax highlighting in the RTF output.
-#
-# Note that which sources are shown also depends on other settings such as
-# SOURCE_BROWSER.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_SOURCE_CODE = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the man page output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
-# classes and files.
-# The default value is: NO.
-
-GENERATE_MAN = NO
-
-# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it. A directory man3 will be created inside the directory specified by
-# MAN_OUTPUT.
-# The default directory is: man.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_OUTPUT = man
-
-# The MAN_EXTENSION tag determines the extension that is added to the generated
-# man pages. In case the manual section does not start with a number, the number
-# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
-# optional.
-# The default value is: .3.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_EXTENSION = .3
-
-# The MAN_SUBDIR tag determines the name of the directory created within
-# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
-# MAN_EXTENSION with the initial . removed.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_SUBDIR =
-
-# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
-# will generate one additional man file for each entity documented in the real
-# man page(s). These additional files only source the real man page, but without
-# them the man command would be unable to find the correct page.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_LINKS = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the XML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
-# captures the structure of the code including all documentation.
-# The default value is: NO.
-
-GENERATE_XML = NO
-
-# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: xml.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_OUTPUT = xml
-
-# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
-# listings (including syntax highlighting and cross-referencing information) to
-# the XML output. Note that enabling this will significantly increase the size
-# of the XML output.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_PROGRAMLISTING = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to the DOCBOOK output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
-# that can be used to generate PDF.
-# The default value is: NO.
-
-GENERATE_DOCBOOK = NO
-
-# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
-# front of it.
-# The default directory is: docbook.
-# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
-
-DOCBOOK_OUTPUT = docbook
-
-# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
-# program listings (including syntax highlighting and cross-referencing
-# information) to the DOCBOOK output. Note that enabling this will significantly
-# increase the size of the DOCBOOK output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
-
-DOCBOOK_PROGRAMLISTING = NO
-
-#---------------------------------------------------------------------------
-# Configuration options for the AutoGen Definitions output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
-# AutoGen Definitions (see http://autogen.sf.net) file that captures the
-# structure of the code including all documentation. Note that this feature is
-# still experimental and incomplete at the moment.
-# The default value is: NO.
-
-GENERATE_AUTOGEN_DEF = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the Perl module output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
-# file that captures the structure of the code including all documentation.
-#
-# Note that this feature is still experimental and incomplete at the moment.
-# The default value is: NO.
-
-GENERATE_PERLMOD = NO
-
-# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
-# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
-# output from the Perl module output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_LATEX = NO
-
-# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
-# formatted so it can be parsed by a human reader. This is useful if you want to
-# understand what is going on. On the other hand, if this tag is set to NO, the
-# size of the Perl module output will be much smaller and Perl will parse it
-# just the same.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_PRETTY = YES
-
-# The names of the make variables in the generated doxyrules.make file are
-# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
-# so different doxyrules.make files included by the same Makefile don't
-# overwrite each other's variables.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_MAKEVAR_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
-#---------------------------------------------------------------------------
-
-# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
-# C-preprocessor directives found in the sources and include files.
-# The default value is: YES.
-
-ENABLE_PREPROCESSING = YES
-
-# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
-# in the source code. If set to NO, only conditional compilation will be
-# performed. Macro expansion can be done in a controlled way by setting
-# EXPAND_ONLY_PREDEF to YES.
-# The default value is: NO.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-MACRO_EXPANSION = NO
-
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
-# the macro expansion is limited to the macros specified with the PREDEFINED and
-# EXPAND_AS_DEFINED tags.
-# The default value is: NO.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-EXPAND_ONLY_PREDEF = NO
-
-# If the SEARCH_INCLUDES tag is set to YES, the include files in the
-# INCLUDE_PATH will be searched if a #include is found.
-# The default value is: YES.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-SEARCH_INCLUDES = YES
-
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by the
-# preprocessor.
-# This tag requires that the tag SEARCH_INCLUDES is set to YES.
-
-INCLUDE_PATH = ../../opentrack
-
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
-# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will be
-# used.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-INCLUDE_FILE_PATTERNS =
-
-# The PREDEFINED tag can be used to specify one or more macro names that are
-# defined before the preprocessor is started (similar to the -D option of e.g.
-# gcc). The argument of the tag is a list of macros of the form: name or
-# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
-# is assumed. To prevent a macro definition from being undefined via #undef or
-# recursively expanded use the := operator instead of the = operator.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-PREDEFINED =
-
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
-# tag can be used to specify a list of macro names that should be expanded. The
-# macro definition that is found in the sources will be used. Use the PREDEFINED
-# tag if you want to use a different macro definition that overrules the
-# definition found in the source code.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-EXPAND_AS_DEFINED =
-
-# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
-# remove all references to function-like macros that are alone on a line, have
-# an all uppercase name, and do not end with a semicolon. Such function macros
-# are typically used for boiler-plate code, and will confuse the parser if not
-# removed.
-# The default value is: YES.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-SKIP_FUNCTION_MACROS = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to external references
-#---------------------------------------------------------------------------
-
-# The TAGFILES tag can be used to specify one or more tag files. For each tag
-# file the location of the external documentation should be added. The format of
-# a tag file without this location is as follows:
-# TAGFILES = file1 file2 ...
-# Adding location for the tag files is done as follows:
-# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where loc1 and loc2 can be relative or absolute paths or URLs. See the
-# section "Linking to external documentation" for more information about the use
-# of tag files.
-# Note: Each tag file must have a unique name (where the name does NOT include
-# the path). If a tag file is not located in the directory in which doxygen is
-# run, you must also specify the path to the tagfile here.
-
-TAGFILES =
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
-# tag file that is based on the input files it reads. See section "Linking to
-# external documentation" for more information about the usage of tag files.
-
-GENERATE_TAGFILE =
-
-# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
-# the class index. If set to NO, only the inherited external classes will be
-# listed.
-# The default value is: NO.
-
-ALLEXTERNALS = NO
-
-# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
-# in the modules index. If set to NO, only the current project's groups will be
-# listed.
-# The default value is: YES.
-
-EXTERNAL_GROUPS = YES
-
-# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
-# the related pages index. If set to NO, only the current project's pages will
-# be listed.
-# The default value is: YES.
-
-EXTERNAL_PAGES = YES
-
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of 'which perl').
-# The default file (with absolute path) is: /usr/bin/perl.
-
-PERL_PATH = /usr/bin/perl
-
-#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
-#---------------------------------------------------------------------------
-
-# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
-# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
-# NO turns the diagrams off. Note that this option also works with HAVE_DOT
-# disabled, but it is recommended to install and use dot, since it yields more
-# powerful graphs.
-# The default value is: YES.
-
-CLASS_DIAGRAMS = YES
-
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see:
-# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
-# default search path.
-
-MSCGEN_PATH =
-
-# You can include diagrams made with dia in doxygen documentation. Doxygen will
-# then run dia to produce the diagram and insert it in the documentation. The
-# DIA_PATH tag allows you to specify the directory where the dia binary resides.
-# If left empty dia is assumed to be found in the default search path.
-
-DIA_PATH =
-
-# If set to YES the inheritance and collaboration graphs will hide inheritance
-# and usage relations if the target is undocumented or is not a class.
-# The default value is: YES.
-
-HIDE_UNDOC_RELATIONS = YES
-
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz (see:
-# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
-# Bell Labs. The other options in this section have no effect if this option is
-# set to NO
-# The default value is: NO.
-
-HAVE_DOT = YES
-
-# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
-# to run in parallel. When set to 0 doxygen will base this on the number of
-# processors available in the system. You can set it explicitly to a value
-# larger than 0 to get control over the balance between CPU load and processing
-# speed.
-# Minimum value: 0, maximum value: 32, default value: 0.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_NUM_THREADS = 3
-
-# When you want a differently looking font in the dot files that doxygen
-# generates you can specify the font name using DOT_FONTNAME. You need to make
-# sure dot is able to find the font, which can be done by putting it in a
-# standard location or by setting the DOTFONTPATH environment variable or by
-# setting DOT_FONTPATH to the directory containing the font.
-# The default value is: Helvetica.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTNAME = Sans
-
-# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
-# dot graphs.
-# Minimum value: 4, maximum value: 24, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTSIZE = 10
-
-# By default doxygen will tell dot to use the default font as specified with
-# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
-# the path where dot can find it using this tag.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTPATH =
-
-# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
-# each documented class showing the direct and indirect inheritance relations.
-# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CLASS_GRAPH = YES
-
-# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
-# graph for each documented class showing the direct and indirect implementation
-# dependencies (inheritance, containment, and class references variables) of the
-# class with other documented classes.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-COLLABORATION_GRAPH = YES
-
-# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
-# groups, showing the direct groups dependencies.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GROUP_GRAPHS = YES
-
-# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
-# collaboration diagrams in a style similar to the OMG's Unified Modeling
-# Language.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-UML_LOOK = NO
-
-# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
-# class node. If there are many fields or methods and many nodes the graph may
-# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
-# number of items for each type to make the size more manageable. Set this to 0
-# for no limit. Note that the threshold may be exceeded by 50% before the limit
-# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
-# but if the number exceeds 15, the total amount of fields shown is limited to
-# 10.
-# Minimum value: 0, maximum value: 100, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-UML_LIMIT_NUM_FIELDS = 10
-
-# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
-# collaboration graphs will show the relations between templates and their
-# instances.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-TEMPLATE_RELATIONS = YES
-
-# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
-# YES then doxygen will generate a graph for each documented file showing the
-# direct and indirect include dependencies of the file with other documented
-# files.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INCLUDE_GRAPH = YES
-
-# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
-# set to YES then doxygen will generate a graph for each documented file showing
-# the direct and indirect include dependencies of the file with other documented
-# files.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INCLUDED_BY_GRAPH = YES
-
-# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
-# dependency graph for every global function or class method.
-#
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable call graphs for selected
-# functions only using the \callgraph command. Disabling a call graph can be
-# accomplished by means of the command \hidecallgraph.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CALL_GRAPH = YES
-
-# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
-# dependency graph for every global function or class method.
-#
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable caller graphs for selected
-# functions only using the \callergraph command. Disabling a caller graph can be
-# accomplished by means of the command \hidecallergraph.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CALLER_GRAPH = NO
-
-# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
-# hierarchy of all classes instead of a textual one.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GRAPHICAL_HIERARCHY = YES
-
-# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
-# dependencies a directory has on other directories in a graphical way. The
-# dependency relations are determined by the #include relations between the
-# files in the directories.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DIRECTORY_GRAPH = YES
-
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot. For an explanation of the image formats see the section
-# output formats in the documentation of the dot tool (Graphviz (see:
-# http://www.graphviz.org/)).
-# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
-# to make the SVG files visible in IE 9+ (other browsers do not have this
-# requirement).
-# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
-# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
-# png:gdiplus:gdiplus.
-# The default value is: png.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_IMAGE_FORMAT = png
-
-# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
-# enable generation of interactive SVG images that allow zooming and panning.
-#
-# Note that this requires a modern browser other than Internet Explorer. Tested
-# and working are Firefox, Chrome, Safari, and Opera.
-# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
-# the SVG files visible. Older versions of IE do not have SVG support.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INTERACTIVE_SVG = YES
-
-# The DOT_PATH tag can be used to specify the path where the dot tool can be
-# found. If left blank, it is assumed the dot tool can be found in the path.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_PATH =
-
-# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the \dotfile
-# command).
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOTFILE_DIRS =
-
-# The MSCFILE_DIRS tag can be used to specify one or more directories that
-# contain msc files that are included in the documentation (see the \mscfile
-# command).
-
-MSCFILE_DIRS =
-
-# The DIAFILE_DIRS tag can be used to specify one or more directories that
-# contain dia files that are included in the documentation (see the \diafile
-# command).
-
-DIAFILE_DIRS =
-
-# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
-# path where java can find the plantuml.jar file. If left blank, it is assumed
-# PlantUML is not used or called during a preprocessing step. Doxygen will
-# generate a warning when it encounters a \startuml command in this case and
-# will not generate output for the diagram.
-
-PLANTUML_JAR_PATH =
-
-# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
-# configuration file for plantuml.
-
-PLANTUML_CFG_FILE =
-
-# When using plantuml, the specified paths are searched for files specified by
-# the !include statement in a plantuml block.
-
-PLANTUML_INCLUDE_PATH =
-
-# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
-# that will be shown in the graph. If the number of nodes in a graph becomes
-# larger than this value, doxygen will truncate the graph, which is visualized
-# by representing a node as a red box. Note that doxygen if the number of direct
-# children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
-# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
-# Minimum value: 0, maximum value: 10000, default value: 50.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_GRAPH_MAX_NODES = 300
-
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
-# generated by dot. A depth value of 3 means that only nodes reachable from the
-# root by following a path via at most 3 edges will be shown. Nodes that lay
-# further from the root node will be omitted. Note that setting this option to 1
-# or 2 may greatly reduce the computation time needed for large code bases. Also
-# note that the size of a graph can be further restricted by
-# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
-# Minimum value: 0, maximum value: 1000, default value: 0.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-MAX_DOT_GRAPH_DEPTH = 0
-
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not seem
-# to support this out of the box.
-#
-# Warning: Depending on the platform used, enabling this option may lead to
-# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
-# read).
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_TRANSPARENT = NO
-
-# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
-# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10) support
-# this, this feature is disabled by default.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_MULTI_TARGETS = YES
-
-# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
-# explaining the meaning of the various boxes and arrows in the dot generated
-# graphs.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GENERATE_LEGEND = YES
-
-# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
-# files that are used to generate the various graphs.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_CLEANUP = YES
diff --git a/ext-falcon-bms-linear-acc/CMakeLists.txt b/ext-falcon-bms-linear-acc/CMakeLists.txt
deleted file mode 100644
index 5f56b15e..00000000
--- a/ext-falcon-bms-linear-acc/CMakeLists.txt
+++ /dev/null
@@ -1 +0,0 @@
-otr_module(ext-falcon-bms-acceleration)
diff --git a/ext-falcon-bms-linear-acc/FlightData.h b/ext-falcon-bms-linear-acc/FlightData.h
deleted file mode 100644
index 10183917..00000000
--- a/ext-falcon-bms-linear-acc/FlightData.h
+++ /dev/null
@@ -1,577 +0,0 @@
-#ifndef _FLIGHT_DATA_H
-#define _FLIGHT_DATA_H
-
-
-#define FLIGHTDATA_VERSION 117
-// changelog:
-// 110: initial BMS 4.33 version
-// 111: added SysTest to LightBits3
-// 112: added MCAnnounced to LightBits3
-// 113: added AllLampBits2OnExceptCarapace to LightBits2 and AllLampBits3OnExceptCarapace to LightBits3
-// 114: renamed WOW LightBit to ONGROUND, added "real" (AFM) WOW to LightBits3
-// 115: renamed "real" WOW in MLGWOW, added NLGWOW
-// 116: bitfields are now unsigned instead of signed
-// 117: added ATF_Not_Engaged to LightBits3
-
-// *** "FalconSharedMemoryArea" ***
-class FlightData
-{
-public:
- // GENERAL NOTE FOR ALL LIGHTBITS:
- //
- // The lightbits contain status about whether a lamp is activated or deactivated. A *blinking* lamp
- // is always activated, even if it is in the "off" phase of the blinking! To check whether an activated
- // lamp is blinking or just "on", use the BlinkBits in FlightData2. A blinkbit does NOT alternate on/off
- // either, it will just state *if* a lamp is blinking. This construct might seem strange at 1st sight,
- // but only like this it can be guaranteed that even low-freq shared mem readers will pick up the info
- // about blinking lamps correctly. Obviously, it is up to the external program to implement the actual
- // blinking logic/freq etc.
- //
- // Summary:
- // a) The LightBit says "lamp is active (LightBit 1) or inactive (LightBit 0)".
- // b) The BlinkBit says "if the lamp is active (see LightBit 1), is it steady (BlinkBit 0)
- // or is it blinking (BlinkBit 1)"
- // c) If a lamp has no BlinkBit, it is always assumed to be steady if active (LightBit 1).
-
- enum LightBits
- {
- MasterCaution = 0x1, // Left eyebrow
-
- // Brow Lights
- TF = 0x2, // Left eyebrow
- OXY_BROW = 0x4, // repurposed for eyebrow OXY LOW (was OBS, unused)
- EQUIP_HOT = 0x8, // Caution light; repurposed for cooling fault (was: not used)
- ONGROUND = 0x10, // True if on ground: this is not a lamp bit!
- ENG_FIRE = 0x20, // Right eyebrow; upper half of split face lamp
- CONFIG = 0x40, // Stores config, caution panel
- HYD = 0x80, // Right eyebrow; see also OIL (this lamp is not split face)
- Flcs_ABCD = 0x100, // TEST panel FLCS channel lamps; repurposed, was OIL (see HYD; that lamp is not split face)
- FLCS = 0x200, // Right eyebrow; was called DUAL which matches block 25, 30/32 and older 40/42
- CAN = 0x400, // Right eyebrow
- T_L_CFG = 0x800, // Right eyebrow
-
- // AOA Indexers
- AOAAbove = 0x1000,
- AOAOn = 0x2000,
- AOABelow = 0x4000,
-
- // Refuel/NWS
- RefuelRDY = 0x8000,
- RefuelAR = 0x10000,
- RefuelDSC = 0x20000,
-
- // Caution Lights
- FltControlSys = 0x40000,
- LEFlaps = 0x80000,
- EngineFault = 0x100000,
- Overheat = 0x200000,
- FuelLow = 0x400000,
- Avionics = 0x800000,
- RadarAlt = 0x1000000,
- IFF = 0x2000000,
- ECM = 0x4000000,
- Hook = 0x8000000,
- NWSFail = 0x10000000,
- CabinPress = 0x20000000,
-
- AutoPilotOn = 0x40000000, // TRUE if is AP on. NB: This is not a lamp bit!
- TFR_STBY = 0x80000000, // MISC panel; lower half of split face TFR lamp
-
- // Used with the MAL/IND light code to light up "everything"
- // please update this if you add/change bits!
- AllLampBitsOn = 0xBFFFFFEF
- };
-
- enum LightBits2
- {
- // Threat Warning Prime
- HandOff = 0x1,
- Launch = 0x2,
- PriMode = 0x4,
- Naval = 0x8,
- Unk = 0x10,
- TgtSep = 0x20,
-
- // EWS
- Go = 0x40, // On and operating normally
- NoGo = 0x80, // On but malfunction present
- Degr = 0x100, // Status message: AUTO DEGR
- Rdy = 0x200, // Status message: DISPENSE RDY
- ChaffLo = 0x400, // Bingo chaff quantity reached
- FlareLo = 0x800, // Bingo flare quantity reached
-
- // Aux Threat Warning
- AuxSrch = 0x1000,
- AuxAct = 0x2000,
- AuxLow = 0x4000,
- AuxPwr = 0x8000,
-
- // ECM
- EcmPwr = 0x10000,
- EcmFail = 0x20000,
-
- // Caution Lights
- FwdFuelLow = 0x40000,
- AftFuelLow = 0x80000,
-
- EPUOn = 0x100000, // EPU panel; run light
- JFSOn = 0x200000, // Eng Jet Start panel; run light
-
- // Caution panel
- SEC = 0x400000,
- OXY_LOW = 0x800000,
- PROBEHEAT = 0x1000000,
- SEAT_ARM = 0x2000000,
- BUC = 0x4000000,
- FUEL_OIL_HOT = 0x8000000,
- ANTI_SKID = 0x10000000,
-
- TFR_ENGAGED = 0x20000000, // MISC panel; upper half of split face TFR lamp
- GEARHANDLE = 0x40000000, // Lamp in gear handle lights on fault or gear in motion
- ENGINE = 0x80000000, // Lower half of right eyebrow ENG FIRE/ENGINE lamp
-
- // Used with the MAL/IND light code to light up "everything"
- // please update this if you add/change bits!
- AllLampBits2On = 0xFFFFF03F,
- AllLampBits2OnExceptCarapace = AllLampBits2On ^ HandOff ^ Launch ^ PriMode ^ Naval ^ Unk ^ TgtSep ^ AuxSrch ^ AuxAct ^ AuxLow ^ AuxPwr
- };
-
- enum LightBits3
- {
- // Elec panel
- FlcsPmg = 0x1,
- MainGen = 0x2,
- StbyGen = 0x4,
- EpuGen = 0x8,
- EpuPmg = 0x10,
- ToFlcs = 0x20,
- FlcsRly = 0x40,
- BatFail = 0x80,
-
- // EPU panel
- Hydrazine = 0x100,
- Air = 0x200,
-
- // Caution panel
- Elec_Fault = 0x400,
- Lef_Fault = 0x800,
-
- OnGround = 0x1000, // weight-on-wheels
- FlcsBitRun = 0x2000, // FLT CONTROL panel RUN light (used to be Multi-engine fire light)
- FlcsBitFail = 0x4000, // FLT CONTROL panel FAIL light (used to be Lock light Cue; non-F-16)
- DbuWarn = 0x8000, // Right eyebrow DBU ON cell; was Shoot light cue; non-F16
- NoseGearDown = 0x10000, // Landing gear panel; on means down and locked
- LeftGearDown = 0x20000, // Landing gear panel; on means down and locked
- RightGearDown = 0x40000, // Landing gear panel; on means down and locked
- ParkBrakeOn = 0x100000, // Parking brake engaged; NOTE: not a lamp bit
- Power_Off = 0x200000, // Set if there is no electrical power. NB: not a lamp bit
-
- // Caution panel
- cadc = 0x400000,
-
- // Left Aux console
- SpeedBrake = 0x800000, // True if speed brake is in anything other than stowed position
-
- // Threat Warning Prime - additional bits
- SysTest = 0x1000000,
-
- // Master Caution WILL come up (actual lightBit has 3sec delay like in RL),
- // usable for cockpit builders with RL equipment which has a delay on its own.
- // Will be set to false again as soon as the MasterCaution bit is set.
- MCAnnounced = 0x2000000,
-
- //MLGWOW is only for AFM , it means WOW switches on MLG are triggered => FLCS switches to WOWPitchRockGain
- MLGWOW = 0x4000000,
- NLGWOW = 0x8000000,
-
- ATF_Not_Engaged = 0x10000000,
-
- // Free bits in LightBits3
- //0x20000000,
- //0x40000000,
- //0x80000000,
-
- // Used with the MAL/IND light code to light up "everything"
- // please update this if you add/change bits!
- AllLampBits3On = 0x1147EFFF,
- AllLampBits3OnExceptCarapace = AllLampBits3On ^ SysTest
- };
-
- enum HsiBits
- {
- ToTrue = 0x01, // HSI_FLAG_TO_TRUE == 1, TO
- IlsWarning = 0x02, // HSI_FLAG_ILS_WARN
- CourseWarning = 0x04, // HSI_FLAG_CRS_WARN
- Init = 0x08, // HSI_FLAG_INIT
- TotalFlags = 0x10, // HSI_FLAG_TOTAL_FLAGS; never set
- ADI_OFF = 0x20, // ADI OFF Flag
- ADI_AUX = 0x40, // ADI AUX Flag
- ADI_GS = 0x80, // ADI GS FLAG
- ADI_LOC = 0x100, // ADI LOC FLAG
- HSI_OFF = 0x200, // HSI OFF Flag
- BUP_ADI_OFF = 0x400, // Backup ADI Off Flag
- VVI = 0x800, // VVI OFF Flag
- AOA = 0x1000, // AOA OFF Flag
- AVTR = 0x2000, // AVTR Light
- OuterMarker = 0x4000, // MARKER beacon light for outer marker
- MiddleMarker = 0x8000, // MARKER beacon light for middle marker
- FromTrue = 0x10000, // HSI_FLAG_TO_TRUE == 2, FROM
-
- Flying = 0x80000000, // true if player is attached to an aircraft (i.e. not in UI state). NOTE: Not a lamp bit
-
- // Used with the MAL/IND light code to light up "everything"
- // please update this is you add/change bits!
- AllLampHsiBitsOn = 0xE000
- };
-
- // These are outputs from the sim
- // Note: some two-engine values removed in this version for compatibility
- // reasons.
- float x; // Ownship North (Ft)
- float y; // Ownship East (Ft)
- float z; // Ownship Down (Ft) --- NOTE: use FlightData2 AAUZ for barometric altitude!
- float xDot; // Ownship North Rate (ft/sec)
- float yDot; // Ownship East Rate (ft/sec)
- float zDot; // Ownship Down Rate (ft/sec)
- float alpha; // Ownship AOA (Degrees)
- float beta; // Ownship Beta (Degrees)
- float gamma; // Ownship Gamma (Radians)
- float pitch; // Ownship Pitch (Radians)
- float roll; // Ownship Pitch (Radians)
- float yaw; // Ownship Pitch (Radians)
- float mach; // Ownship Mach number
- float kias; // Ownship Indicated Airspeed (Knots)
- float vt; // Ownship True Airspeed (Ft/Sec)
- float gs; // Ownship Normal Gs
- float windOffset; // Wind delta to FPM (Radians)
- float nozzlePos; // Ownship engine nozzle percent open (0-100)
- //float nozzlePos2; // MOVED TO FlightData2! Ownship engine nozzle2 percent open (0-100)
- float internalFuel; // Ownship internal fuel (Lbs)
- float externalFuel; // Ownship external fuel (Lbs)
- float fuelFlow; // Ownship fuel flow (Lbs/Hour)
- float rpm; // Ownship engine rpm (Percent 0-103)
- //float rpm2; // MOVED TO FlightData2! Ownship engine rpm2 (Percent 0-103)
- float ftit; // Ownship Forward Turbine Inlet Temp (Degrees C)
- //float ftit2; // MOVED TO FlightData2! Ownship Forward Turbine Inlet Temp2 (Degrees C)
- float gearPos; // Ownship Gear position 0 = up, 1 = down;
- float speedBrake; // Ownship speed brake position 0 = closed, 1 = 60 Degrees open
- float epuFuel; // Ownship EPU fuel (Percent 0-100)
- float oilPressure; // Ownship Oil Pressure (Percent 0-100)
- //float oilPressure2; // MOVED TO FlightData2! Ownship Oil Pressure2 (Percent 0-100)
- unsigned int lightBits; // Cockpit Indicator Lights, one bit per bulb. See enum
-
- // These are inputs. Use them carefully
- // NB: these do not work when TrackIR device is enabled
- // NB2: launch falcon with the '-head' command line parameter to activate !
- float headPitch; // Head pitch offset from design eye (radians)
- float headRoll; // Head roll offset from design eye (radians)
- float headYaw; // Head yaw offset from design eye (radians)
-
- // new lights
- unsigned int lightBits2; // Cockpit Indicator Lights, one bit per bulb. See enum
- unsigned int lightBits3; // Cockpit Indicator Lights, one bit per bulb. See enum
-
- // chaff/flare
- float ChaffCount; // Number of Chaff left
- float FlareCount; // Number of Flare left
-
- // landing gear
- float NoseGearPos; // Position of the nose landinggear; caution: full down values defined in dat files
- float LeftGearPos; // Position of the left landinggear; caution: full down values defined in dat files
- float RightGearPos; // Position of the right landinggear; caution: full down values defined in dat files
-
- // ADI values
- float AdiIlsHorPos; // Position of horizontal ILS bar
- float AdiIlsVerPos; // Position of vertical ILS bar
-
- // HSI states
- int courseState; // HSI_STA_CRS_STATE
- int headingState; // HSI_STA_HDG_STATE
- int totalStates; // HSI_STA_TOTAL_STATES; never set
-
- // HSI values
- float courseDeviation; // HSI_VAL_CRS_DEVIATION
- float desiredCourse; // HSI_VAL_DESIRED_CRS
- float distanceToBeacon; // HSI_VAL_DISTANCE_TO_BEACON
- float bearingToBeacon; // HSI_VAL_BEARING_TO_BEACON
- float currentHeading; // HSI_VAL_CURRENT_HEADING
- float desiredHeading; // HSI_VAL_DESIRED_HEADING
- float deviationLimit; // HSI_VAL_DEV_LIMIT
- float halfDeviationLimit; // HSI_VAL_HALF_DEV_LIMIT
- float localizerCourse; // HSI_VAL_LOCALIZER_CRS
- float airbaseX; // HSI_VAL_AIRBASE_X
- float airbaseY; // HSI_VAL_AIRBASE_Y
- float totalValues; // HSI_VAL_TOTAL_VALUES; never set
-
- float TrimPitch; // Value of trim in pitch axis, -0.5 to +0.5
- float TrimRoll; // Value of trim in roll axis, -0.5 to +0.5
- float TrimYaw; // Value of trim in yaw axis, -0.5 to +0.5
-
- // HSI flags
- unsigned int hsiBits; // HSI flags
-
- //DED Lines
- char DEDLines[5][26]; //25 usable chars
- char Invert[5][26]; //25 usable chars
-
- //PFL Lines
- char PFLLines[5][26]; //25 usable chars
- char PFLInvert[5][26]; //25 usable chars
-
- //TacanChannel
- int UFCTChan, AUXTChan;
-
- // RWR
- int RwrObjectCount;
- int RWRsymbol[40];
- float bearing[40];
- unsigned long missileActivity[40];
- unsigned long missileLaunch[40];
- unsigned long selected[40];
- float lethality[40];
- unsigned long newDetection[40];
-
- //fuel values
- float fwd, aft, total;
-
- void SetLightBit (unsigned int newBit) {lightBits |= newBit;};
- void ClearLightBit(unsigned int newBit) { lightBits &= ~newBit; };
- bool IsSet(unsigned int newBit) { return ((lightBits & newBit) ? true : false); };
-
- void SetLightBit2(unsigned int newBit) { lightBits2 |= newBit; };
- void ClearLightBit2(unsigned int newBit) { lightBits2 &= ~newBit; };
- bool IsSet2(unsigned int newBit) { return ((lightBits2 & newBit) ? true : false); };
-
- void SetLightBit3(unsigned int newBit) { lightBits3 |= newBit; };
- void ClearLightBit3(unsigned int newBit) { lightBits3 &= ~newBit; };
- bool IsSet3(unsigned int newBit) { return ((lightBits3 & newBit) ? true : false); };
-
- void SetHsiBit(unsigned int newBit) { hsiBits |= newBit; };
- void ClearHsiBit(unsigned int newBit) { hsiBits &= ~newBit; };
- bool IsSetHsi(unsigned int newBit) { return ((hsiBits & newBit) ? true : false); };
-
- int VersionNum; // Version of FlightData mem area
-
- // New values added here for header file compatibility but not implemented
- // in this version of the code at present.
- float headX; // Head X offset from design eye (feet)
- float headY; // Head Y offset from design eye (feet)
- float headZ; // Head Z offset from design eye (feet)
-
- int MainPower; // Main Power switch state, 0=down, 1=middle, 2=up
-};
-
-
-// OSB capture for MFD button labeling
-
-#define OSB_STRING_LENGTH 8 // currently strings appear to be max 7 printing chars
-
-typedef struct {
- char line1[OSB_STRING_LENGTH];
- char line2[OSB_STRING_LENGTH];
- bool inverted;
-} OsbLabel;
-
-
-// *** "FalconSharedOsbMemoryArea" ***
-class OSBData
-{
-public:
- OsbLabel leftMFD[20];
- OsbLabel rightMFD[20];
-};
-
-
-#define FLIGHTDATA2_VERSION 9
-// changelog:
-// 1: initial BMS 4.33 version
-// 2: added AltCalReading, altBits, BupUhfPreset, powerBits, blinkBits, cmdsMode
-// 3: added VersionNum, hydPressureA/B, cabinAlt, BupUhfFreq, currentTime, vehicleACD
-// 4: added fuelflow2
-// 5: added RwrInfo, lefPos, tefPos
-// 6: added vtolPos
-// 7: bit fields are now unsigned instead of signed
-// 8: increased RwrInfo size to 512
-// 9: added human pilot names and their status in a session
-
-// do NOT change these w/o crosschecking the BMS code
-#define RWRINFO_SIZE 512
-#define CALLSIGN_LEN 12
-#define MAX_CALLSIGNS 32
-
-// *** "FalconSharedMemoryArea2" ***
-class FlightData2
-{
-public:
-
- // TACAN
- enum TacanSources
- {
- UFC = 0,
- AUX = 1,
- NUMBER_OF_SOURCES = 2,
- };
- enum TacanBits
- {
- band = 0x01, // true in this bit position if band is X
- mode = 0x02, // true in this bit position if domain is air to air
- };
-
- // ALTIMETER
- enum AltBits
- {
- CalType = 0x01, // true if calibration in inches of Mercury (Hg), false if in hectoPascal (hPa)
- PneuFlag = 0x02, // true if PNEU flag is visible
- };
-
- // POWER
- enum PowerBits
- {
- BusPowerBattery = 0x01, // true if at least the battery bus is powered
- BusPowerEmergency = 0x02, // true if at least the emergency bus is powered
- BusPowerEssential = 0x04, // true if at least the essential bus is powered
- BusPowerNonEssential = 0x08, // true if at least the non-essential bus is powered
- MainGenerator = 0x10, // true if the main generator is online
- StandbyGenerator = 0x20, // true if the standby generator is online
- JetFuelStarter = 0x40, // true if JFS is running, can be used for magswitch
- };
-
- // BLINKING LIGHTS - only indicating *IF* a lamp is blinking, not implementing the actual on/off/blinking pattern logic!
- enum BlinkBits
- {
- // currently working
- OuterMarker = 0x01, // defined in HsiBits - slow flashing for outer marker
- MiddleMarker = 0x02, // defined in HsiBits - fast flashing for middle marker
- PROBEHEAT = 0x04, // defined in LightBits2 - probeheat system is tested
- AuxSrch = 0x08, // defined in LightBits2 - search function in NOT activated and a search radar is painting ownship
- Launch = 0x10, // defined in LightBits2 - missile is fired at ownship
- PriMode = 0x20, // defined in LightBits2 - priority mode is enabled but more than 5 threat emitters are detected
- Unk = 0x40, // defined in LightBits2 - unknown is not active but EWS detects unknown radar
-
- // not working yet, defined for future use
- Elec_Fault = 0x80, // defined in LightBits3 - non-resetting fault
- OXY_BROW = 0x100, // defined in LightBits - monitor fault during Obogs
- EPUOn = 0x200, // defined in LightBits3 - abnormal EPU operation
- JFSOn_Slow = 0x400, // defined in LightBits3 - slow blinking: non-critical failure
- JFSOn_Fast = 0x800, // defined in LightBits3 - fast blinking: critical failure
- };
-
- // CMDS mode state
- enum CmdsModes
- {
- CmdsOFF = 0,
- CmdsSTBY = 1,
- CmdsMAN = 2,
- CmdsSEMI = 3,
- CmdsAUTO = 4,
- CmdsBYP = 5,
- };
-
- // HSI/eHSI mode state
- enum NavModes
- {
- ILS_TACAN = 0,
- TACAN = 1,
- NAV = 2,
- ILS_NAV = 3,
- };
-
- // human pilot state
- enum FlyStates
- {
- IN_UI = 0, // UI - in the UI
- LOADING = 1, // UI>3D - loading the sim data
- WAITING = 2, // UI>3D - waiting for other players
- FLYING = 3, // 3D - flying
- DEAD = 4, // 3D>Dead - dead, waiting to respawn
- UNKNOWN = 5, // ???
- };
-
- // VERSION 1
- float nozzlePos2; // Ownship engine nozzle2 percent open (0-100)
- float rpm2; // Ownship engine rpm2 (Percent 0-103)
- float ftit2; // Ownship Forward Turbine Inlet Temp2 (Degrees C)
- float oilPressure2; // Ownship Oil Pressure2 (Percent 0-100)
- unsigned char navMode; // current mode selected for HSI/eHSI, see NavModes enum for details
- float AAUZ; // Ownship barometric altitude given by AAU (depends on calibration)
- char tacanInfo[NUMBER_OF_SOURCES]; // Tacan band/mode settings for UFC and AUX COMM
-
- // VERSION 2 / 7
- int AltCalReading; // barometric altitude calibration (depends on CalType)
- unsigned int altBits; // various altimeter bits, see AltBits enum for details
- unsigned int powerBits; // Ownship power bus / generator states, see PowerBits enum for details
- unsigned int blinkBits; // Cockpit indicator lights blink status, see BlinkBits enum for details
- // NOTE: these bits indicate only *if* a lamp is blinking, in addition to the
- // existing on/off bits. It's up to the external program to implement the
- // *actual* blinking.
- int cmdsMode; // Ownship CMDS mode state, see CmdsModes enum for details
- int BupUhfPreset; // BUP UHF channel preset
-
- // VERSION 3
- int BupUhfFreq; // BUP UHF channel frequency
- float cabinAlt; // Ownship cabin altitude
- float hydPressureA; // Ownship Hydraulic Pressure A
- float hydPressureB; // Ownship Hydraulic Pressure B
- int currentTime; // Current time in seconds (max 60 * 60 * 24)
- short vehicleACD; // Ownship ACD index number, i.e. which aircraft type are we flying.
- int VersionNum; // Version of FlightData2 mem area
-
- // VERSION 4
- float fuelFlow2; // Ownship fuel flow2 (Lbs/Hour)
-
- // VERSION 5 / 8
- char RwrInfo[RWRINFO_SIZE]; // New RWR Info
- float lefPos; // Ownship LEF position
- float tefPos; // Ownship TEF position
-
- // VERSION 6
- float vtolPos; // Ownship VTOL exhaust angle
-
- // VERSION 9
- char pilotsOnline; // Number of pilots in an MP session
- char pilotsCallsign[MAX_CALLSIGNS][CALLSIGN_LEN]; // List of pilots callsign connected to an MP session
- char pilotsStatus[MAX_CALLSIGNS]; // Status of the MP pilots, see enum FlyStates
-
- // TACAN
- // setters for internal use only
- void SetUfcTacanToAA(bool t) { if (t) { tacanInfo[UFC] |= mode; } else { tacanInfo[UFC] &= ~mode; } }
- void SetAuxTacanToAA(bool t) { if (t) { tacanInfo[AUX] |= mode; } else { tacanInfo[AUX] &= ~mode; } }
- void SetUfcTacanToX(bool t) { if (t) { tacanInfo[UFC] |= band; } else { tacanInfo[UFC] &= ~band; } }
- void SetAuxTacanToX(bool t) { if (t) { tacanInfo[AUX] |= band; } else { tacanInfo[AUX] &= ~band; } }
-
- // getters for external reader programs
- bool UfcTacanIsAA(void) {return ((tacanInfo[UFC] & mode) ? true : false); }
- bool AuxTacanIsAA(void) {return ((tacanInfo[AUX] & mode) ? true : false); }
- bool UfcTacanIsX(void) {return ((tacanInfo[UFC] & band) ? true : false); }
- bool AuxTacanIsX(void) {return ((tacanInfo[AUX] & band) ? true : false); }
-
- // ALTIMETER
- void SetAltBit(unsigned int newBit) { altBits |= newBit; };
- void ClearAltBit(unsigned int newBit) { altBits &= ~newBit; };
- bool IsSetAlt(unsigned int newBit) { return ((altBits & newBit) ? true : false); };
-
- // POWER
- void SetPowerBit(unsigned int newBit) { powerBits |= newBit; };
- void ClearPowerBit(unsigned int newBit) { powerBits &= ~newBit; };
- bool IsSetPower(unsigned int newBit) { return ((powerBits & newBit) ? true : false); };
-
- // BLINKING LIGHTS
- void SetBlinkBit(unsigned int newBit) { blinkBits |= newBit; };
- void ClearBlinkBit(unsigned int newBit) { blinkBits &= ~newBit; };
- bool IsSetBlink(unsigned int newBit) { return ((blinkBits & newBit) ? true : false); };
-
- // CMDS mode state
- void SetCmdsMode(int newMode) {cmdsMode = newMode;};
- int GetCmdsMode(void) {return cmdsMode;};
-
- // HSI/eHSI mode state
- void SetNavMode(int newMode) {navMode = newMode;};
- int GetNavMode(void) {return navMode;};
-};
-
-
-extern OSBData cockpitOSBData; // "FalconSharedOsbMemoryArea"
-extern FlightData cockpitFlightData; // "FalconSharedMemoryArea"
-extern FlightData2 cockpitFlightData2; // "FalconSharedMemoryArea2"
-
-#endif
diff --git a/ext-falcon-bms-linear-acc/bms-shm.cpp b/ext-falcon-bms-linear-acc/bms-shm.cpp
deleted file mode 100644
index be7d94ff..00000000
--- a/ext-falcon-bms-linear-acc/bms-shm.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "FlightData.h"
-
diff --git a/ext-falcon-bms-linear-acc/bms-shm.hpp b/ext-falcon-bms-linear-acc/bms-shm.hpp
deleted file mode 100644
index 3f59c932..00000000
--- a/ext-falcon-bms-linear-acc/bms-shm.hpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#pragma once
-
diff --git a/ext-falcon-bms-linear-acc/falcon-bms-dialog.cpp b/ext-falcon-bms-linear-acc/falcon-bms-dialog.cpp
deleted file mode 100644
index d4268a06..00000000
--- a/ext-falcon-bms-linear-acc/falcon-bms-dialog.cpp
+++ /dev/null
@@ -1,9 +0,0 @@
-#include "falcon-bms-dialog.hpp"
-
-void falcon_bms_acceleration_dialog::register_extension(IExtension&)
-{
-}
-
-void falcon_bms_acceleration_dialog::unregister_extension()
-{
-}
diff --git a/ext-falcon-bms-linear-acc/falcon-bms-dialog.hpp b/ext-falcon-bms-linear-acc/falcon-bms-dialog.hpp
deleted file mode 100644
index 9aa5baa3..00000000
--- a/ext-falcon-bms-linear-acc/falcon-bms-dialog.hpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-#include "falcon-bms-ext.hpp"
-
-struct falcon_bms_acceleration_dialog : IExtensionDialog
-{
-public:
- void register_extension(IExtension& ext) override;
- void unregister_extension() override;
-};
diff --git a/ext-falcon-bms-linear-acc/falcon-bms-ext.cpp b/ext-falcon-bms-linear-acc/falcon-bms-ext.cpp
deleted file mode 100644
index b6a32540..00000000
--- a/ext-falcon-bms-linear-acc/falcon-bms-ext.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#include "falcon-bms-ext.hpp"
-
-IExtension::event_mask falcon_bms_acceleration_ext::hook_types()
-{
- return IExtension::on_finished;
-}
-
-falcon_bms_acceleration_ext::falcon_bms_acceleration_ext()
-{
-}
-
-void falcon_bms_acceleration_ext::process_finished(Pose& p)
-{
- (void)p;
-}
diff --git a/ext-falcon-bms-linear-acc/falcon-bms-ext.hpp b/ext-falcon-bms-linear-acc/falcon-bms-ext.hpp
deleted file mode 100644
index e08d5377..00000000
--- a/ext-falcon-bms-linear-acc/falcon-bms-ext.hpp
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma once
-
-#include "api/plugin-api.hpp"
-
-struct falcon_bms_acceleration_ext : IExtension
-{
- event_mask hook_types() override;
- falcon_bms_acceleration_ext();
- void process_finished(Pose&p) override;
- module_status initialize() override { return status_ok(); }
-};
diff --git a/ext-falcon-bms-linear-acc/falcon-bms-metadata.cpp b/ext-falcon-bms-linear-acc/falcon-bms-metadata.cpp
deleted file mode 100644
index fa95447c..00000000
--- a/ext-falcon-bms-linear-acc/falcon-bms-metadata.cpp
+++ /dev/null
@@ -1,11 +0,0 @@
-#include "falcon-bms-metadata.hpp"
-
-QString falcon_bms_acceleration_metadata::name()
-{
- return "Falcon BMS linear acceleration display";
-}
-
-QIcon falcon_bms_acceleration_metadata::icon()
-{
- return QIcon();
-}
diff --git a/ext-falcon-bms-linear-acc/falcon-bms-metadata.hpp b/ext-falcon-bms-linear-acc/falcon-bms-metadata.hpp
deleted file mode 100644
index eea0d6bd..00000000
--- a/ext-falcon-bms-linear-acc/falcon-bms-metadata.hpp
+++ /dev/null
@@ -1,9 +0,0 @@
-#pragma once
-
-#include "falcon-bms-ext.hpp"
-
-struct falcon_bms_acceleration_metadata : Metadata
-{
- QString name() override;
- QIcon icon() override;
-};
diff --git a/ext-falcon-bms-linear-acc/module.cpp b/ext-falcon-bms-linear-acc/module.cpp
deleted file mode 100644
index 80a9553d..00000000
--- a/ext-falcon-bms-linear-acc/module.cpp
+++ /dev/null
@@ -1,5 +0,0 @@
-#include "falcon-bms-ext.hpp"
-#include "falcon-bms-dialog.hpp"
-#include "falcon-bms-metadata.hpp"
-
-OPENTRACK_DECLARE_EXTENSION(falcon_bms_acceleration_ext, falcon_bms_acceleration_dialog, falcon_bms_acceleration_metadata)
diff --git a/filter-accela-hamilton/CMakeLists.txt b/filter-accela-hamilton/CMakeLists.txt
new file mode 100644
index 00000000..825cef5e
--- /dev/null
+++ b/filter-accela-hamilton/CMakeLists.txt
@@ -0,0 +1,2 @@
+otr_module(filter-accela-hamilton)
+target_link_libraries(opentrack-filter-accela-hamilton opentrack-spline)
diff --git a/filter-accela-hamilton/accela_hamilton.cpp b/filter-accela-hamilton/accela_hamilton.cpp
new file mode 100644
index 00000000..7fde90b8
--- /dev/null
+++ b/filter-accela-hamilton/accela_hamilton.cpp
@@ -0,0 +1,121 @@
+/* Copyright (c) 2012-2015 Stanislaw Halik
+ * Copyright (c) 2023-2024 Michael Welter
+ *
+ * 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 "accela_hamilton.h"
+#include "api/plugin-api.hpp"
+#include "opentrack/defs.hpp"
+
+#include <algorithm>
+
+#include "compat/math.hpp"
+#include "compat/hamilton-tools.h"
+#include "compat/math-imports.hpp"
+#include "compat/time.hpp"
+
+
+accela_hamilton::accela_hamilton()
+{
+ s.make_splines(spline_rot, spline_pos);
+}
+
+
+void accela_hamilton::filter(const double* input, double *output)
+{
+ constexpr double EPSILON = 1e-15F;
+
+ const tQuat current_rot = QuatFromYPR(input + Yaw);
+ const tVector current_pos(input[TX], input[TY], input[TZ]);
+
+ if (unlikely(first_run))
+ {
+ first_run = false;
+ last_rotation = current_rot;
+ last_position = current_pos;
+ t.start();
+#if defined DEBUG_ACCELA
+ debug_max = 0;
+ debug_timer.start();
+#endif
+ return;
+ }
+
+ const double pos_thres{s.pos_smoothing};
+ const double pos_dz{ s.pos_deadzone};
+
+ const double dt = t.elapsed_seconds();
+ t.start();
+
+ // Position
+ {
+ const tVector delta = current_pos - last_position;
+ const double delta_len = VectorLength(delta);
+ const tVector delta_normed = delta_len>0. ? delta/delta_len : tVector(); // Zero vector when length was zero.
+ const double gain = dt*spline_pos.get_value_no_save(std::max(0., delta_len-pos_dz) / pos_thres);
+ const tVector output_pos = last_position + (delta_normed * gain);
+ output[TX] = output_pos.v[0];
+ output[TY] = output_pos.v[1];
+ output[TZ] = output_pos.v[2];
+ last_position = output_pos;
+ }
+
+ // Zoom smoothing:
+ const double zoomed_smoothing = [this](double output_z) {
+ // Local copies because accessing settings involves thread synchronization
+ // and I don't like this in the middle of math.
+ const double max_zoomed_smoothing {s.max_zoomed_smoothing};
+ const double max_z {s.max_z};
+ // Movement toward the monitor is negative. Negate and clamp it to get a positive value
+ const double z = std::clamp(-output_z, 0., max_z);
+ return max_zoomed_smoothing * z / (max_z + EPSILON);
+ }(output[TZ]);
+
+ const double rot_dz{ s.rot_deadzone};
+ const double rot_thres = double{s.rot_smoothing} + zoomed_smoothing;
+
+ // Rotation
+ {
+ // Inter/extrapolates along the arc between the old and new orientation.
+ // It's basically a quaternion spherical linear interpolation, where the
+ // accela gain x dt is the blending parameter. Might actually overshoot
+ // the new orientation, but that's fine.
+
+ // Compute rotation angle and axis which brings the previous orientation to the current rotation
+ const double angle = AngleBetween(last_rotation, current_rot);
+ // Apply the Accela gain magic. The "gain_angle" is the desired rotation from the old orientation
+ // towards the current. Then alpha is the blending factor for the SLerp operation. It is normalized
+ // to the range [0,1] where 1 corresponds to the current orientation, i.e. it is the fractional
+ // rotation relative to the "gain_angle". EPSILON is added to prevent division by zero.
+ // Additionally we use std::min(1., ...) to clamp the blending. alpha>1 would probably not make much
+ // sense since it would mean extrapolation beyond the current orientation. And it would be a rare
+ // edge case. Secondly idk if Slerp supports alpha>1.
+ const double normalized_angle = std::max(0., angle - rot_dz) / rot_thres;
+ const double gain_angle = dt*spline_rot.get_value_no_save(std::abs(normalized_angle));
+ const double alpha = std::min(1., gain_angle / (angle + EPSILON));
+ // Rotate toward the measured orientation.
+ const tQuat output_rot = Slerp(last_rotation, current_rot, alpha);
+ // And back to Euler angles
+ QuatToYPR(output_rot, output + Yaw);
+ last_rotation = output_rot;
+ }
+}
+
+namespace detail::accela_hamilton {
+
+void settings_accela_hamilton::make_splines(spline& rot, spline& pos)
+{
+ rot.clear(); pos.clear();
+
+ for (const auto& val : rot_gains)
+ rot.add_point({ val.x, val.y });
+
+ for (const auto& val : pos_gains)
+ pos.add_point({ val.x, val.y });
+}
+
+} // ns detail::accela_hamilton
+
+OPENTRACK_DECLARE_FILTER(accela_hamilton, dialog_accela_hamilton, accela_hamiltonDll)
diff --git a/filter-accela-hamilton/accela_hamilton.h b/filter-accela-hamilton/accela_hamilton.h
new file mode 100644
index 00000000..1afbfd8c
--- /dev/null
+++ b/filter-accela-hamilton/accela_hamilton.h
@@ -0,0 +1,69 @@
+/* Copyright (c) 2012-2015 Stanislaw Halik
+ * Copyright (c) 2023-2024 Michael Welter
+ *
+ * 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 "ui_accela_hamilton_filtercontrols.h"
+
+#include "accela_hamilton_settings.hpp"
+#include "api/plugin-api.hpp"
+#include "compat/timer.hpp"
+#include "compat/variance.hpp"
+#include "compat/hamilton-tools.h"
+
+#include <QMutex>
+#include <QTimer>
+
+
+//#define DEBUG_ACCELA
+
+struct accela_hamilton : IFilter
+{
+ accela_hamilton();
+ void filter(const double* input, double *output) override;
+ void center() override { first_run = true; }
+ spline spline_rot, spline_pos;
+ module_status initialize() override { return status_ok(); }
+private:
+ settings_accela_hamilton s;
+ tVector last_position = {};
+ tQuat last_rotation = {};
+ Timer t;
+#if defined DEBUG_ACCELA
+ Timer debug_timer;
+ double debug_max;
+ variance var;
+#endif
+ bool first_run = true;
+};
+
+class dialog_accela_hamilton : public IFilterDialog
+{
+ Q_OBJECT
+public:
+ dialog_accela_hamilton();
+ void register_filter(IFilter*) override {}
+ void unregister_filter() override {}
+ void save() override;
+ void reload() override;
+ bool embeddable() noexcept override { return true; }
+ void set_buttons_visible(bool x) override;
+private:
+ Ui::AccelaUICdialog_accela ui;
+ settings_accela_hamilton s;
+private slots:
+ void doOK();
+ void doCancel();
+};
+
+class accela_hamiltonDll : public Metadata
+{
+ Q_OBJECT
+
+ QString name() override { return tr("AccelaHamilton"); }
+ QIcon icon() override { return QIcon(":/images/filter-16.png"); }
+};
diff --git a/filter-accela-hamilton/accela_hamilton_dialog.cpp b/filter-accela-hamilton/accela_hamilton_dialog.cpp
new file mode 100644
index 00000000..711535f8
--- /dev/null
+++ b/filter-accela-hamilton/accela_hamilton_dialog.cpp
@@ -0,0 +1,99 @@
+/* Copyright (c) 2012-2015 Stanislaw Halik
+ * Copyright (c) 2023-2024 Michael Welter
+ *
+ * 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 "accela_hamilton.h"
+#include <cmath>
+#include <QDebug>
+#include <algorithm>
+#include <QDoubleSpinBox>
+#include "api/plugin-api.hpp"
+#include "spline/spline-widget.hpp"
+#include <QDialog>
+
+using namespace options;
+
+dialog_accela_hamilton::dialog_accela_hamilton()
+{
+ ui.setupUi(this);
+
+ connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
+ connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
+
+ tie_setting(s.rot_smoothing, ui.rotation_slider);
+ tie_setting(s.pos_smoothing, ui.translation_slider);
+ tie_setting(s.rot_deadzone, ui.rot_dz_slider);
+ tie_setting(s.pos_deadzone, ui.trans_dz_slider);
+
+ tie_setting(s.rot_smoothing, ui.rot_gain, [](const slider_value& s) { return tr("%1°").arg(s, 0, 'g', 4); });
+ tie_setting(s.pos_smoothing, ui.trans_gain, [](const slider_value& s) { return tr("%1mm").arg(s, 0, 'g', 4); });
+ tie_setting(s.rot_deadzone, ui.rot_dz, [](const slider_value& s) { return tr("%1°").arg(s, 0, 'g', 4); });
+ tie_setting(s.pos_deadzone, ui.trans_dz, [](const slider_value& s) { return tr("%1mm").arg(s); });
+
+ tie_setting(s.max_zoomed_smoothing, ui.max_zoomed_smoothing);
+ tie_setting(s.max_zoomed_smoothing, ui.lb_max_zoomed_smoothing, [](double x)
+ { return tr("%1°").arg(x, 0, 'g', 3);});
+
+ tie_setting(s.max_z, ui.max_z);
+ tie_setting(s.max_z, ui.lb_max_z, [](double x)
+ { return tr("%1mm").arg(x, 0, 'g', 3);});
+
+//#define SPLINE_ROT_DEBUG
+//#define SPLINE_TRANS_DEBUG
+
+#if defined SPLINE_ROT_DEBUG || defined SPLINE_TRANS_DEBUG
+ {
+ spline rot, pos;
+ s.make_splines(rot, pos);
+
+#ifdef SPLINE_ROT_DEBUG
+ QDialog dr;
+ spline_widget r(&dr);
+ dr.setWindowTitle("Accela rotation gain"); r.set_preview_only(true); r.setEnabled(true);
+ r.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); r.set_config(&rot);
+ r.setFixedSize(1024, 600);
+ dr.show();
+ dr.exec();
+#endif
+
+#ifdef SPLINE_TRANS_DEBUG
+ QDialog dt;
+ spline_widget t(&dt);
+ dt.setWindowTitle("Accela translation gain"); t.set_preview_only(true); t.setEnabled(true);
+ dt.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); t.set_config(&pos);
+ t.setFixedSize(1024, 600);
+ dt.show();
+ dt.exec();
+#endif
+ }
+#endif
+}
+
+void dialog_accela_hamilton::doOK()
+{
+ save();
+ close();
+}
+
+void dialog_accela_hamilton::doCancel()
+{
+ close();
+}
+
+void dialog_accela_hamilton::save()
+{
+ s.b->save();
+}
+
+void dialog_accela_hamilton::reload()
+{
+ s.b->reload();
+}
+
+void dialog_accela_hamilton::set_buttons_visible(bool x)
+{
+ ui.buttonBox->setVisible(x);
+}
diff --git a/filter-accela-hamilton/accela_hamilton_filtercontrols.ui b/filter-accela-hamilton/accela_hamilton_filtercontrols.ui
new file mode 100644
index 00000000..f6049444
--- /dev/null
+++ b/filter-accela-hamilton/accela_hamilton_filtercontrols.ui
@@ -0,0 +1,485 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AccelaUICdialog_accela</class>
+ <widget class="QWidget" name="AccelaUICdialog_accela">
+ <property name="windowModality">
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>632</width>
+ <height>628</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>550</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Filter settings</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../gui/opentrack-res.qrc">
+ <normaloff>:/images/filter-16.png</normaloff>:/images/filter-16.png</iconset>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <property name="topMargin">
+ <number>5</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QTextBrowser" name="textBrowser">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>130</height>
+ </size>
+ </property>
+ <property name="acceptDrops">
+ <bool>false</bool>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QAbstractScrollArea::AdjustToContents</enum>
+ </property>
+ <property name="html">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Accela + Hamilton Filter&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;For both rotations and positions: No movement occurs within the dead zone.&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;For larger movements, rotation and translation occurs toward the new measurement. The strength is determined by the Smoothing setting and the builtin Accela gain curves.&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;When you lean forward, smoothing is added up to the set maximum at the distance of Max Z.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Rotation Smoothing (Added when zoomed)</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <property name="topMargin">
+ <number>4</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>9</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>2</number>
+ </property>
+ <item row="1" column="2">
+ <widget class="QSlider" name="max_z">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>25</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Max Added</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QSlider" name="max_zoomed_smoothing">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>49</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksAbove</enum>
+ </property>
+ <property name="tickInterval">
+ <number>40</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="lb_max_zoomed_smoothing">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>0° </string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="lb_max_z">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>0mm</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Max Z</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Position filtering (X, Y, Z - translation)</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="topMargin">
+ <number>4</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>9</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>2</number>
+ </property>
+ <item row="1" column="2">
+ <widget class="QSlider" name="trans_dz_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>20</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Smoothing</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QSlider" name="translation_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>29</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksAbove</enum>
+ </property>
+ <property name="tickInterval">
+ <number>40</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="trans_gain">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>0mm</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="trans_dz">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>0mm</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Deadzone</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ <zorder>trans_dz_slider</zorder>
+ <zorder>translation_slider</zorder>
+ <zorder>trans_dz</zorder>
+ <zorder>label_6</zorder>
+ <zorder>trans_gain</zorder>
+ <zorder>label</zorder>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QGroupBox" name="groupBox44">
+ <property name="title">
+ <string>Rotation filtering (Yaw, pitch, and roll)</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="topMargin">
+ <number>4</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>9</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>2</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblSensYaw_6">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Smoothing</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="rot_gain">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>0°</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QSlider" name="rotation_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>49</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksAbove</enum>
+ </property>
+ <property name="tickInterval">
+ <number>50</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Deadzone</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="rot_dz">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>0°</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QSlider" name="rot_dz_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>20</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>rotation_slider</tabstop>
+ <tabstop>rot_dz_slider</tabstop>
+ <tabstop>translation_slider</tabstop>
+ <tabstop>trans_dz_slider</tabstop>
+ </tabstops>
+ <resources>
+ <include location="../gui/opentrack-res.qrc"/>
+ </resources>
+ <connections/>
+ <slots>
+ <slot>startEngineClicked()</slot>
+ <slot>stopEngineClicked()</slot>
+ <slot>cameraSettingsClicked()</slot>
+ </slots>
+</ui>
diff --git a/filter-accela-hamilton/accela_hamilton_settings.hpp b/filter-accela-hamilton/accela_hamilton_settings.hpp
new file mode 100644
index 00000000..71ddf479
--- /dev/null
+++ b/filter-accela-hamilton/accela_hamilton_settings.hpp
@@ -0,0 +1,63 @@
+#pragma once
+
+#include "spline/spline.hpp"
+#include "options/options.hpp"
+
+namespace detail::accela_hamilton {
+
+using namespace options;
+
+// ------------------------------------
+// debug knobs
+// ------------------------------------
+
+//#define DEBUG_ACCELA
+//#define SPLINE_ROT_DEBUG
+//#define SPLINE_TRANS_DEBUG
+
+struct settings_accela_hamilton : opts
+{
+ struct gains
+ {
+ double x, y;
+ };
+
+ static constexpr gains const rot_gains[] {
+ { 9, 300 },
+ { 8, 200 },
+ { 5, 100 },
+ { 2.5, 35 },
+ { 1.5, 8 },
+ { 1, 1.5 },
+ { .5, .4 },
+ };
+
+ static constexpr gains const pos_gains[] {
+ { 9, 200 },
+ { 8, 150 },
+ { 7, 110 },
+ { 5, 60 },
+ { 3, 24 },
+ { 2, 7.5 },
+ { 1.66, 4.5 },
+ { 1.33, 2.25 },
+ { .66, .75 },
+ { .33, .375 },
+ { 0, 0 },
+ };
+
+ static void make_splines(spline& rot, spline& pos);
+
+ value<slider_value> rot_smoothing { b, "rotation-sensitivity", { 1.5, .05, 2.5 } },
+ pos_smoothing { b, "translation-sensitivity", { 1., .05, 1.5 } },
+ rot_deadzone { b, "rotation-deadzone", { .03, 0, .2 } },
+ pos_deadzone { b, "translation-deadzone", { .1, 0, 1 } },
+ max_zoomed_smoothing { b, "max_zoomed_smoothing", { .0, .0, 10. } },
+ max_z { b, "max-z", { 10., .0, 30. } };
+
+ settings_accela_hamilton() : opts("accela-hamilton-sliders") {}
+};
+
+} // ns detail::accela_hamilton
+
+using detail::accela_hamilton::settings_accela_hamilton;
diff --git a/filter-accela-hamilton/lang/de_DE.ts b/filter-accela-hamilton/lang/de_DE.ts
new file mode 100644
index 00000000..1a27c27b
--- /dev/null
+++ b/filter-accela-hamilton/lang/de_DE.ts
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>AccelaUICdialog_accela</name>
+ <message>
+ <source>Filter settings</source>
+ <translation>Filter-Einstellungen</translation>
+ </message>
+ <message>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Accela + Hamilton Filter&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;For both rotations and positions: No movement occurs within the dead zone.&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;For larger movements, rotation and translation occurs toward the new measurement. The strength is determined by the Smoothing setting and the builtin Accela gain curves.&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;When you lean forward, smoothing is added up to the set maximum at the distance of Max Z.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Accela + Hamilton Filter&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Für Rotation und Position: Es erfolgt keine Bewegung innerhalb des Totbereichs.&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Bei größeren Bewegungen erfolgen Rotation und Übersetzung zugunsten der neuen Messung. Die Stärke wird durch die Glättungseinstellung und die vorgegebenen Accela-Verstärkungskurven bestimmt.&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Beim Vorbeugen wird eine Glättung bis zum eingestellten Maximum der Distanz Max Z hinzugefügt.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>Rotation Smoothing (Added when zoomed)</source>
+ <translation>Rotationsglättung (hinzugefügt beim Zoomen)</translation>
+ </message>
+ <message>
+ <source>Max Added</source>
+ <translation>Maximal hinzugefügt</translation>
+ </message>
+ <message>
+ <source>0° </source>
+ <translation>0° </translation>
+ </message>
+ <message>
+ <source>0mm</source>
+ <translation>0 mm</translation>
+ </message>
+ <message>
+ <source>Max Z</source>
+ <translation>Maximales Z</translation>
+ </message>
+ <message>
+ <source>Position filtering (X, Y, Z - translation)</source>
+ <translation>Positionsfilterung (X, Y, Z-Übersetzung)</translation>
+ </message>
+ <message>
+ <source>Smoothing</source>
+ <translation>Glättung</translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation>Totbereich</translation>
+ </message>
+ <message>
+ <source>Rotation filtering (Yaw, pitch, and roll)</source>
+ <translation>Rotationsfilterung (Gieren, Nicken und Rollen)</translation>
+ </message>
+ <message>
+ <source>0°</source>
+ <translation>0°</translation>
+ </message>
+</context>
+<context>
+ <name>accela_hamiltonDll</name>
+ <message>
+ <source>AccelaHamilton</source>
+ <translation>AccelaHamilton</translation>
+ </message>
+</context>
+<context>
+ <name>dialog_accela_hamilton</name>
+ <message>
+ <source>%1°</source>
+ <translation>%1°</translation>
+ </message>
+ <message>
+ <source>%1mm</source>
+ <translation>%1 mm</translation>
+ </message>
+</context>
+</TS>
diff --git a/filter-accela-hamilton/lang/nl_NL.ts b/filter-accela-hamilton/lang/nl_NL.ts
new file mode 100644
index 00000000..13315922
--- /dev/null
+++ b/filter-accela-hamilton/lang/nl_NL.ts
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>AccelaUICdialog_accela</name>
+ <message>
+ <source>Filter settings</source>
+ <translation type="unfinished">Filter-instellingen</translation>
+ </message>
+ <message>
+ <source>Smoothing</source>
+ <translation type="unfinished">Verzachten</translation>
+ </message>
+ <message>
+ <source>Position filtering (X, Y, Z - translation)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0mm</source>
+ <translation type="unfinished">0mm</translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished">Deadzone</translation>
+ </message>
+ <message>
+ <source>Rotation filtering (Yaw, pitch, and roll)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0°</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Rotation Smoothing (Added when zoomed)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0° </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max Added</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Accela + Hamilton Filter&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;For both rotations and positions: No movement occurs within the dead zone.&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;For larger movements, rotation and translation occurs toward the new measurement. The strength is determined by the Smoothing setting and the builtin Accela gain curves.&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;When you lean forward, smoothing is added up to the set maximum at the distance of Max Z.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>accela_hamiltonDll</name>
+ <message>
+ <source>AccelaHamilton</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialog_accela_hamilton</name>
+ <message>
+ <source>%1°</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/filter-accela-hamilton/lang/ru_RU.ts b/filter-accela-hamilton/lang/ru_RU.ts
new file mode 100644
index 00000000..7dd3e1eb
--- /dev/null
+++ b/filter-accela-hamilton/lang/ru_RU.ts
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>AccelaUICdialog_accela</name>
+ <message>
+ <source>Filter settings</source>
+ <translation>ÐаÑтройка фильтра</translation>
+ </message>
+ <message>
+ <source>Smoothing</source>
+ <translation>Сглаживание</translation>
+ </message>
+ <message>
+ <source>Position filtering (X, Y, Z - translation)</source>
+ <translation>Ð¤Ð¸Ð»ÑŒÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ñмещений (X, Y, Z)</translation>
+ </message>
+ <message>
+ <source>0mm</source>
+ <translation>0мм</translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation>ÐœÐµÑ€Ñ‚Ð²Ð°Ñ Ð·Ð¾Ð½Ð°</translation>
+ </message>
+ <message>
+ <source>Rotation filtering (Yaw, pitch, and roll)</source>
+ <translation>Ð¤Ð¸Ð»ÑŒÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¿Ð¾Ð²Ð¾Ñ€Ð¾Ñ‚Ð¾Ð² (РыÑканье, тангаж, крен)</translation>
+ </message>
+ <message>
+ <source>0°</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Rotation Smoothing (Added when zoomed)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0° </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max Added</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Accela + Hamilton Filter&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;For both rotations and positions: No movement occurs within the dead zone.&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;For larger movements, rotation and translation occurs toward the new measurement. The strength is determined by the Smoothing setting and the builtin Accela gain curves.&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;When you lean forward, smoothing is added up to the set maximum at the distance of Max Z.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>accela_hamiltonDll</name>
+ <message>
+ <source>AccelaHamilton</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialog_accela_hamilton</name>
+ <message>
+ <source>%1°</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/filter-accela-hamilton/lang/stub.ts b/filter-accela-hamilton/lang/stub.ts
new file mode 100644
index 00000000..2cd62df5
--- /dev/null
+++ b/filter-accela-hamilton/lang/stub.ts
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>AccelaUICdialog_accela</name>
+ <message>
+ <source>Filter settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Smoothing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Position filtering (X, Y, Z - translation)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Rotation filtering (Yaw, pitch, and roll)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0°</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Rotation Smoothing (Added when zoomed)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0° </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max Added</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Accela + Hamilton Filter&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;For both rotations and positions: No movement occurs within the dead zone.&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;For larger movements, rotation and translation occurs toward the new measurement. The strength is determined by the Smoothing setting and the builtin Accela gain curves.&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;When you lean forward, smoothing is added up to the set maximum at the distance of Max Z.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>accela_hamiltonDll</name>
+ <message>
+ <source>AccelaHamilton</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialog_accela_hamilton</name>
+ <message>
+ <source>%1°</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/filter-accela-hamilton/lang/zh_CN.ts b/filter-accela-hamilton/lang/zh_CN.ts
new file mode 100644
index 00000000..43a12f5a
--- /dev/null
+++ b/filter-accela-hamilton/lang/zh_CN.ts
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>AccelaUICdialog_accela</name>
+ <message>
+ <source>Filter settings</source>
+ <translation>过滤器设置</translation>
+ </message>
+ <message>
+ <source>Rotation filtering (Yaw, pitch, and roll)</source>
+ <translation>旋转过滤器 (å航, 俯仰, 滚转)</translation>
+ </message>
+ <message>
+ <source>Smoothing</source>
+ <translation type="unfinished">平滑</translation>
+ </message>
+ <message>
+ <source>0°</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation>死区</translation>
+ </message>
+ <message>
+ <source>Position filtering (X, Y, Z - translation)</source>
+ <translation>æ–¹ä½è¿‡æ»¤å™¨ (X, Y, Z - å˜æ¢)</translation>
+ </message>
+ <message>
+ <source>0mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Rotation Smoothing (Added when zoomed)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0° </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max Added</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Accela + Hamilton Filter&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;For both rotations and positions: No movement occurs within the dead zone.&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;For larger movements, rotation and translation occurs toward the new measurement. The strength is determined by the Smoothing setting and the builtin Accela gain curves.&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;When you lean forward, smoothing is added up to the set maximum at the distance of Max Z.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>accela_hamiltonDll</name>
+ <message>
+ <source>AccelaHamilton</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialog_accela_hamilton</name>
+ <message>
+ <source>%1°</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/filter-accela/accela-settings.hpp b/filter-accela/accela-settings.hpp
index 63173aa8..b2f62a43 100644
--- a/filter-accela/accela-settings.hpp
+++ b/filter-accela/accela-settings.hpp
@@ -1,8 +1,11 @@
#pragma once
+#include "spline/spline.hpp"
#include "options/options.hpp"
+
+namespace detail::accela {
+
using namespace options;
-#include "spline/spline.hpp"
// ------------------------------------
// debug knobs
@@ -19,8 +22,7 @@ struct settings_accela : opts
double x, y;
};
- static constexpr inline gains const rot_gains[16] =
- {
+ static constexpr gains const rot_gains[] {
{ 9, 300 },
{ 8, 200 },
{ 5, 100 },
@@ -30,8 +32,7 @@ struct settings_accela : opts
{ .5, .4 },
};
- static constexpr inline gains const pos_gains[16] =
- {
+ static constexpr gains const pos_gains[] {
{ 9, 200 },
{ 8, 150 },
{ 7, 110 },
@@ -47,10 +48,14 @@ struct settings_accela : opts
static void make_splines(spline& rot, spline& pos);
- value<slider_value> rot_smoothing { b, "rotation-sensitivity", slider_value(1.5, .05, 2.5) },
- pos_smoothing { b, "translation-sensitivity", slider_value(1., .05, 1.5) },
- rot_deadzone { b, "rotation-deadzone", slider_value(.03, 0, .2) },
- pos_deadzone { b, "translation-deadzone", slider_value(.1, 0, 1) };
+ value<slider_value> rot_smoothing { b, "rotation-sensitivity", { 1.5, .05, 2.5 } },
+ pos_smoothing { b, "translation-sensitivity", { 1., .05, 1.5 } },
+ rot_deadzone { b, "rotation-deadzone", { .03, 0, .2 } },
+ pos_deadzone { b, "translation-deadzone", { .1, 0, 1 } };
settings_accela() : opts("accela-sliders") {}
};
+
+} // ns detail::accela
+
+using detail::accela::settings_accela;
diff --git a/filter-accela/ftnoir_accela_filtercontrols.ui b/filter-accela/ftnoir_accela_filtercontrols.ui
index ce9f52a2..899f4920 100644
--- a/filter-accela/ftnoir_accela_filtercontrols.ui
+++ b/filter-accela/ftnoir_accela_filtercontrols.ui
@@ -10,7 +10,7 @@
<x>0</x>
<y>0</y>
<width>550</width>
- <height>275</height>
+ <height>279</height>
</rect>
</property>
<property name="sizePolicy">
@@ -276,6 +276,19 @@
<zorder>label</zorder>
</widget>
</item>
+ <item row="3" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
<item row="2" column="0">
<widget class="QLabel" name="label_9">
<property name="sizePolicy">
@@ -321,18 +334,18 @@
</property>
</widget>
</item>
- <item row="3" column="0">
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <item row="4" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
</property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>1</height>
+ </size>
</property>
- </widget>
+ </spacer>
</item>
</layout>
</widget>
diff --git a/filter-accela/ftnoir_filter_accela.cpp b/filter-accela/ftnoir_filter_accela.cpp
index 23d71ef0..940ac50c 100644
--- a/filter-accela/ftnoir_filter_accela.cpp
+++ b/filter-accela/ftnoir_filter_accela.cpp
@@ -7,54 +7,54 @@
#include "ftnoir_filter_accela.h"
#include "compat/math.hpp"
#include "api/plugin-api.hpp"
+#include "opentrack/defs.hpp"
#include <algorithm>
#include <QDebug>
#include <QMutexLocker>
#include "compat/math-imports.hpp"
+#include "compat/time.hpp"
-accela::accela() : first_run(true)
+accela::accela()
{
s.make_splines(spline_rot, spline_pos);
}
-template<int N = 3, typename F>
+template<typename F>
never_inline
-static void do_deltas(const double* deltas, double* output, double& smoothed, F&& fun)
+static void do_deltas(const double* deltas, double* output, F&& fun)
{
+ constexpr unsigned N = 3;
+
double norm[N];
+ double dist = 0;
- const double dist = progn(
- double ret = 0;
- for (unsigned k = 0; k < N; k++)
- ret += deltas[k]*deltas[k];
- return sqrt(ret);
- );
+ for (unsigned k = 0; k < N; k++)
+ dist += deltas[k]*deltas[k];
+ dist = sqrt(dist);
- const double value = double(fun(dist));
+ const double value = fun(dist);
for (unsigned k = 0; k < N; k++)
{
- const double c = dist > 1e-6 ? clamp((fabs(deltas[k]) / dist), 0., 1.) : 0;
+ const double c = dist > 1e-6 ? std::clamp((fabs(deltas[k]) / dist), 0., 1.) : 0;
norm[k] = c;
}
- progn(
- double n = 0;
- for (unsigned k = 0; k < N; k++)
- n += norm[k];
+ double n = 0;
+ for (unsigned k = 0; k < N; k++) // NOLINT(modernize-loop-convert)
+ n += norm[k];
- if (n > 1e-6)
- {
- const double ret = 1./n;
- for (unsigned k = 0; k < N; k++)
- norm[k] *= ret;
- }
- else
- for (unsigned k = 0; k < N; k++)
- norm[k] = 0;
- );
+ if (n > 1e-6)
+ {
+ const double ret = 1./n;
+ for (unsigned k = 0; k < N; k++) // NOLINT(modernize-loop-convert)
+ norm[k] *= ret;
+ }
+ else
+ for (unsigned k = 0; k < N; k++) // NOLINT(modernize-loop-convert)
+ norm[k] = 0;
for (unsigned k = 0; k < N; k++)
{
@@ -63,8 +63,19 @@ static void do_deltas(const double* deltas, double* output, double& smoothed, F&
}
}
+template<typename F>
+[[maybe_unused]]
+never_inline
+static void do_delta(double delta, double* output, F&& fun)
+{
+ *output = fun(fabs(delta)) * signum(delta);
+}
+
void accela::filter(const double* input, double *output)
{
+ static constexpr double full_turn = 360.0;
+ static constexpr double half_turn = 180.0;
+
if (unlikely(first_run))
{
first_run = false;
@@ -76,9 +87,6 @@ void accela::filter(const double* input, double *output)
last_output[i] = f;
}
- smoothed_input[0] = 0;
- smoothed_input[1] = 0;
-
t.start();
#if defined DEBUG_ACCELA
debug_max = 0;
@@ -88,20 +96,21 @@ void accela::filter(const double* input, double *output)
return;
}
- const double rot_thres = s.rot_smoothing.to<double>();
- const double pos_thres = s.pos_smoothing.to<double>();
+ const double rot_thres{s.rot_smoothing};
+ const double pos_thres{s.pos_smoothing};
const double dt = t.elapsed_seconds();
t.start();
- const double rot_dz = s.rot_deadzone.to<double>();
- const double pos_dz = s.pos_deadzone.to<double>();
+ const double rot_dz{ s.rot_deadzone};
+ const double pos_dz{ s.pos_deadzone};
// rot
for (unsigned i = 3; i < 6; i++)
{
double d = input[i] - last_output[i];
+ if (fabs(d) > half_turn) d -= copysign(full_turn, d);
if (fabs(d) > rot_dz)
d -= copysign(rot_dz, d);
@@ -111,26 +120,15 @@ void accela::filter(const double* input, double *output)
deltas[i] = d / rot_thres;
}
- do_deltas(&deltas[Yaw], &output[Yaw], smoothed_input[0], [this](double x) {
+#ifdef UI_ACCELA_OLD_STAIRCASE
+ for (int i = Yaw; i <= Roll; i++)
+ do_delta(deltas[i], &output[i], [this](double x) {
+ return spline_rot.get_value_no_save(x);
+ });
+#else
+ do_deltas(&deltas[Yaw], &output[Yaw], [this](double x) {
return spline_rot.get_value_no_save(x);
});
-
-#if defined DEBUG_ACCELA
- var.input(fabs(smoothed_input[0]) + fabs(smoothed_input[1]) + fabs(smoothed_input[2]));
- debug_max = fmax(debug_max, smoothed_input[0]);
-
- using time_units::secs_;
-
- if (debug_timer.is_elapsed(secs_(1)))
- {
- qDebug() << "accela:"
- << "max" << debug_max
- << "mean" << var.avg()
- << "stddev/mean" << var.stddev() / var.avg();
-
- var.clear();
- debug_max = 0;
- }
#endif
// pos
@@ -146,7 +144,7 @@ void accela::filter(const double* input, double *output)
deltas[i] = d / pos_thres;
}
- do_deltas(&deltas[TX], &output[TX], smoothed_input[1], [this](double x) {
+ do_deltas(&deltas[TX], &output[TX], [this](double x) {
return spline_pos.get_value_no_save(x);
});
@@ -156,22 +154,26 @@ void accela::filter(const double* input, double *output)
{
output[k] *= dt;
output[k] += last_output[k];
+ if (k >= Yaw && fabs(output[k]) > half_turn)
+ output[k] -= copysign(full_turn, output[k]);
last_output[k] = output[k];
}
}
+namespace detail::accela {
+
void settings_accela::make_splines(spline& rot, spline& pos)
{
- rot = spline();
- pos = spline();
+ rot.clear(); pos.clear();
for (const auto& val : rot_gains)
- rot.add_point(QPointF(val.x, val.y));
+ rot.add_point({ val.x, val.y });
for (const auto& val : pos_gains)
- pos.add_point(QPointF(val.x, val.y));
+ pos.add_point({ val.x, val.y });
}
-OPENTRACK_DECLARE_FILTER(accela, dialog_accela, accelaDll)
+} // ns detail::accela
+OPENTRACK_DECLARE_FILTER(accela, dialog_accela, accelaDll)
diff --git a/filter-accela/ftnoir_filter_accela.h b/filter-accela/ftnoir_filter_accela.h
index 1d7925dc..0d6dca1b 100644
--- a/filter-accela/ftnoir_filter_accela.h
+++ b/filter-accela/ftnoir_filter_accela.h
@@ -12,14 +12,14 @@
#include "api/plugin-api.hpp"
#include "compat/timer.hpp"
#include "compat/variance.hpp"
-#include "compat/macros.hpp"
#include <QMutex>
#include <QTimer>
-class accela : public IFilter
+//#define DEBUG_ACCELA
+
+struct accela : IFilter
{
-public:
accela();
void filter(const double* input, double *output) override;
void center() override { first_run = true; }
@@ -27,15 +27,14 @@ public:
module_status initialize() override { return status_ok(); }
private:
settings_accela s;
- double last_output[6], deltas[6];
- double smoothed_input[2];
+ double last_output[6] {}, deltas[6] {};
Timer t;
#if defined DEBUG_ACCELA
Timer debug_timer;
double debug_max;
variance var;
#endif
- bool first_run;
+ bool first_run = true;
};
class dialog_accela : public IFilterDialog
@@ -45,9 +44,12 @@ public:
dialog_accela();
void register_filter(IFilter*) override {}
void unregister_filter() override {}
+ void save() override;
+ void reload() override;
+ bool embeddable() noexcept override { return true; }
+ void set_buttons_visible(bool x) override;
private:
Ui::AccelaUICdialog_accela ui;
- void save();
settings_accela s;
private slots:
void doOK();
@@ -56,7 +58,8 @@ private slots:
class accelaDll : public Metadata
{
-public:
- QString name() { return otr_tr("Accela"); }
- QIcon icon() { return QIcon(":/images/filter-16.png"); }
+ Q_OBJECT
+
+ QString name() override { return tr("Accela"); }
+ QIcon icon() override { return QIcon(":/images/filter-16.png"); }
};
diff --git a/filter-accela/ftnoir_filter_accela_dialog.cpp b/filter-accela/ftnoir_filter_accela_dialog.cpp
index 51c31ca4..6a0a35b0 100644
--- a/filter-accela/ftnoir_filter_accela_dialog.cpp
+++ b/filter-accela/ftnoir_filter_accela_dialog.cpp
@@ -13,6 +13,8 @@
#include "spline/spline-widget.hpp"
#include <QDialog>
+using namespace options;
+
dialog_accela::dialog_accela()
{
ui.setupUi(this);
@@ -42,7 +44,7 @@ dialog_accela::dialog_accela()
QDialog dr;
spline_widget r(&dr);
dr.setWindowTitle("Accela rotation gain"); r.set_preview_only(true); r.setEnabled(true);
- r.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); r.setConfig(&rot);
+ r.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); r.set_config(&rot);
r.setFixedSize(1024, 600);
dr.show();
dr.exec();
@@ -52,7 +54,7 @@ dialog_accela::dialog_accela()
QDialog dt;
spline_widget t(&dt);
dt.setWindowTitle("Accela translation gain"); t.set_preview_only(true); t.setEnabled(true);
- dt.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); t.setConfig(&pos);
+ dt.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); t.set_config(&pos);
t.setFixedSize(1024, 600);
dt.show();
dt.exec();
@@ -76,3 +78,13 @@ void dialog_accela::save()
{
s.b->save();
}
+
+void dialog_accela::reload()
+{
+ s.b->reload();
+}
+
+void dialog_accela::set_buttons_visible(bool x)
+{
+ ui.buttonBox->setVisible(x);
+}
diff --git a/filter-accela/lang/de_DE.ts b/filter-accela/lang/de_DE.ts
new file mode 100644
index 00000000..9ab4e8c5
--- /dev/null
+++ b/filter-accela/lang/de_DE.ts
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>AccelaUICdialog_accela</name>
+ <message>
+ <source>Filter settings</source>
+ <translation>Filter-Einstellungen</translation>
+ </message>
+ <message>
+ <source>Rotation filtering (Yaw, pitch, and roll)</source>
+ <translation>Rotationsfilterung (Gieren, Nicken und Rollen)</translation>
+ </message>
+ <message>
+ <source>Smoothing</source>
+ <translation>Glättung</translation>
+ </message>
+ <message>
+ <source>0°</source>
+ <translation>0°</translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation>Totbereich</translation>
+ </message>
+ <message>
+ <source>Position filtering (X, Y, Z - translation)</source>
+ <translation>Positionsfilterung (X, Y, Z-Übersetzung)</translation>
+ </message>
+ <message>
+ <source>0mm</source>
+ <translation>0 mm</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;right&quot;&gt;&lt;br/&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Accela by &lt;/span&gt;&lt;a href=&quot;https://github.com/sthalik&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0057ae;&quot;&gt;Stanisław Halik&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&lt;br/&gt;Thanks to &lt;/span&gt;&lt;a href=&quot;https://github.com/dbaarda&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0057ae;&quot;&gt;Donovan Baarda&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align=&quot;right&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;2012-2017&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;right&quot;&gt;&lt;br/&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Visit &lt;/span&gt;&lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Accela-in-opentrack-2.3&quot;&gt;&lt;span style=&quot; font-size:8pt; text-decoration: underline; color:#0000ff;&quot;&gt;our wiki&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; for description of the settings.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;right&quot;&gt;&lt;br/&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Accela by &lt;/span&gt;&lt;a href=&quot;https://github.com/sthalik&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0057ae;&quot;&gt;Stanisław Halik&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&lt;br/&gt;Thanks to &lt;/span&gt;&lt;a href=&quot;https://github.com/dbaarda&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0057ae;&quot;&gt;Donovan Baarda&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align=&quot;right&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;2012-2017&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;right&quot;&gt;&lt;br/&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;In &lt;/span&gt;&lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Accela-in-opentrack-2.3&quot;&gt;&lt;span style=&quot; font-size:8pt; text-decoration: underline; color:#0000ff;&quot;&gt;unserem Wiki&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; gibt es eine Beschreibung der Einstellungen.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>accelaDll</name>
+ <message>
+ <source>Accela</source>
+ <translation>Accela</translation>
+ </message>
+</context>
+<context>
+ <name>dialog_accela</name>
+ <message>
+ <source>%1°</source>
+ <translation>%1°</translation>
+ </message>
+ <message>
+ <source>%1mm</source>
+ <translation>%1 mm</translation>
+ </message>
+</context>
+</TS>
diff --git a/filter-accela/lang/nl_NL.ts b/filter-accela/lang/nl_NL.ts
index 53f37992..5a4d1f58 100644
--- a/filter-accela/lang/nl_NL.ts
+++ b/filter-accela/lang/nl_NL.ts
@@ -37,6 +37,13 @@
</message>
</context>
<context>
+ <name>accelaDll</name>
+ <message>
+ <source>Accela</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>dialog_accela</name>
<message>
<source>%1°</source>
diff --git a/filter-accela/lang/ru_RU.ts b/filter-accela/lang/ru_RU.ts
index 387f993b..c2eb175d 100644
--- a/filter-accela/lang/ru_RU.ts
+++ b/filter-accela/lang/ru_RU.ts
@@ -37,6 +37,13 @@
</message>
</context>
<context>
+ <name>accelaDll</name>
+ <message>
+ <source>Accela</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>dialog_accela</name>
<message>
<source>%1°</source>
diff --git a/filter-accela/lang/stub.ts b/filter-accela/lang/stub.ts
index a8e48a37..4a3e27ff 100644
--- a/filter-accela/lang/stub.ts
+++ b/filter-accela/lang/stub.ts
@@ -37,6 +37,13 @@
</message>
</context>
<context>
+ <name>accelaDll</name>
+ <message>
+ <source>Accela</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>dialog_accela</name>
<message>
<source>%1°</source>
diff --git a/filter-accela/lang/zh_CN.ts b/filter-accela/lang/zh_CN.ts
index 98e95407..1deb8288 100644
--- a/filter-accela/lang/zh_CN.ts
+++ b/filter-accela/lang/zh_CN.ts
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>AccelaUICdialog_accela</name>
<message>
<source>Filter settings</source>
- <translation type="unfinished"></translation>
+ <translation>过滤器设置</translation>
</message>
<message>
<source>Rotation filtering (Yaw, pitch, and roll)</source>
- <translation type="unfinished"></translation>
+ <translation>旋转过滤器 (å航, 俯仰, 滚转)</translation>
</message>
<message>
<source>Smoothing</source>
- <translation type="unfinished"></translation>
+ <translation type="unfinished">平滑</translation>
</message>
<message>
<source>0°</source>
@@ -21,11 +21,11 @@
</message>
<message>
<source>Deadzone</source>
- <translation type="unfinished"></translation>
+ <translation>死区</translation>
</message>
<message>
<source>Position filtering (X, Y, Z - translation)</source>
- <translation type="unfinished"></translation>
+ <translation>æ–¹ä½è¿‡æ»¤å™¨ (X, Y, Z - å˜æ¢)</translation>
</message>
<message>
<source>0mm</source>
@@ -33,7 +33,39 @@
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;right&quot;&gt;&lt;br/&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Accela by &lt;/span&gt;&lt;a href=&quot;https://github.com/sthalik&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0057ae;&quot;&gt;Stanisław Halik&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&lt;br/&gt;Thanks to &lt;/span&gt;&lt;a href=&quot;https://github.com/dbaarda&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0057ae;&quot;&gt;Donovan Baarda&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align=&quot;right&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;2012-2017&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;right&quot;&gt;&lt;br/&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Visit &lt;/span&gt;&lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Accela-in-opentrack-2.3&quot;&gt;&lt;span style=&quot; font-size:8pt; text-decoration: underline; color:#0000ff;&quot;&gt;our wiki&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; for description of the settings.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
- <translation type="unfinished"></translation>
+ <translation type="unfinished">&lt;html&gt;
+
+&lt;body&gt;
+ &lt;p align=&quot;right&quot;&gt;&lt;br /&gt;
+ &lt;span style=&quot; font-size:10pt;&quot;&gt;Accela by &lt;/span&gt;
+ &lt;a href=&quot;https://github.com/sthalik&quot;&gt;
+ &lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0057ae;&quot;&gt;Stanisław Halik&lt;/span&gt;
+ &lt;/a&gt;
+ &lt;span style=&quot; font-size:10pt;&quot;&gt;&lt;br /&gt;Thanks to &lt;/span&gt;
+ &lt;a href=&quot;https://github.com/dbaarda&quot;&gt;
+ &lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0057ae;&quot;&gt;Donovan Baarda&lt;/span&gt;
+ &lt;/a&gt;
+ &lt;/p&gt;
+ &lt;p align=&quot;right&quot;&gt;
+ &lt;span style=&quot; font-size:10pt;&quot;&gt;2012-2017&lt;/span&gt;
+ &lt;/p&gt;
+ &lt;p align=&quot;right&quot;&gt;&lt;br /&gt;
+ &lt;span style=&quot; font-size:8pt;&quot;&gt;访问&lt;/span&gt;
+ &lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Accela-in-opentrack-2.3&quot;&gt;
+ &lt;span style=&quot; font-size:8pt; text-decoration: underline; color:#0066bb;&quot;&gt;我们的 wiki&lt;/span&gt;
+ &lt;/a&gt;
+ &lt;span style=&quot; font-size:8pt;&quot;&gt; 了解设置的æè¿°.&lt;/span&gt;
+ &lt;/p&gt;
+&lt;/body&gt;
+
+&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>accelaDll</name>
+ <message>
+ <source>Accela</source>
+ <translation></translation>
</message>
</context>
<context>
diff --git a/filter-ewma2/ftnoir_ewma_filtercontrols.ui b/filter-ewma2/ftnoir_ewma_filtercontrols.ui
index e0a98174..513a8ec3 100644
--- a/filter-ewma2/ftnoir_ewma_filtercontrols.ui
+++ b/filter-ewma2/ftnoir_ewma_filtercontrols.ui
@@ -245,10 +245,6 @@
</item>
<item>
<widget class="QLabel" name="label_4">
- <property name="styleSheet">
- <string notr="true">background-color: rgb(214, 214, 214);
-border-color: rgb(0, 0, 0);</string>
- </property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
@@ -257,7 +253,7 @@ border-color: rgb(0, 0, 0);</string>
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; font-size:10pt; font-weight:600;&quot;&gt;Give the filter up to 60 seconds to warm up and stop shaking.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; font-size:10pt; font-weight:600;&quot;&gt;Give the filter a few seconds to warm up and stop shaking.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Min:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Defines the way the filter responds to fast movements;&lt;/span&gt;&lt;/p&gt;
diff --git a/filter-ewma2/ftnoir_filter_ewma2.cpp b/filter-ewma2/ftnoir_filter_ewma2.cpp
index c79596ef..686552ba 100644
--- a/filter-ewma2/ftnoir_filter_ewma2.cpp
+++ b/filter-ewma2/ftnoir_filter_ewma2.cpp
@@ -22,36 +22,36 @@
// to minSmooth at a rate controlled by the powCurve setting.
-ewma::ewma() : first_run(true)
-{
-}
+ewma::ewma() = default;
void ewma::filter(const double *input, double *output)
{
+ static constexpr double full_turn = 360;
+ static constexpr double half_turn = 180;
// Start the timer and initialise filter state if it's not running.
if (first_run)
{
first_run = false;
timer.start();
- for (int i=0;i<6;i++)
- {
- last_output[i] = input[i];
- last_delta[i] = 0;
- last_noise[i] = 0;
- }
+ noise_RC = 0.0;
+ std::copy(input, input + 6, last_output);
+ std::fill(last_delta, last_delta + 6, 0.0);
+ std::fill(last_noise, last_noise + 6, 0.0);
+ return;
}
// Get the time in seconds since last run and restart the timer.
const double dt = timer.elapsed_seconds();
timer.start();
// Calculate delta_alpha and noise_alpha from dt.
+ noise_RC = std::min(noise_RC + dt, noise_RC_max);
double delta_alpha = dt/(dt + delta_RC);
double noise_alpha = dt/(dt + noise_RC);
// scale curve .01->1 where 1.0 is sqrt(norm_noise).
- const double smoothing_scale_curve = static_cast<slider_value>(s.kSmoothingScaleCurve);
+ const double smoothing_scale_curve = *s.kSmoothingScaleCurve;
// min/max smoothing .01->1
- const double min_smoothing = static_cast<slider_value>(s.kMinSmoothing);
- const double max_smoothing = std::fmax(min_smoothing, static_cast<slider_value>(s.kMaxSmoothing));
+ const double min_smoothing{s.kMinSmoothing};
+ const double max_smoothing = std::fmax(min_smoothing, *s.kMaxSmoothing);
// Calculate the new camera position.
for (int i=0;i<6;i++)
@@ -59,8 +59,14 @@ void ewma::filter(const double *input, double *output)
using std::pow;
// Calculate the current and smoothed delta.
- double delta = input[i] - last_output[i];
- last_delta[i] = delta_alpha*delta + (1.0-delta_alpha)*last_delta[i];
+ double input_value = input[i];
+ double delta = input_value - last_output[i];
+ if (i >= 3 && fabs(delta) > half_turn)
+ {
+ delta -= copysign(full_turn, delta);
+ input_value -= copysign(full_turn, input_value);
+ }
+ last_delta[i] += delta_alpha * (delta - last_delta[i]);
// Calculate the current and smoothed noise variance.
double noise = last_delta[i]*last_delta[i];
last_noise[i] = noise_alpha*noise + (1.0-noise_alpha)*last_noise[i];
@@ -72,7 +78,8 @@ void ewma::filter(const double *input, double *output)
// Calculate the dynamic alpha.
double alpha = dt/(dt + RC);
// Calculate the new output position.
- output[i] = last_output[i] = alpha*input[i] + (1.0-alpha)*last_output[i];
+ last_output[i] += alpha * (input_value - last_output[i]);
+ output[i] = last_output[i];
}
}
diff --git a/filter-ewma2/ftnoir_filter_ewma2.h b/filter-ewma2/ftnoir_filter_ewma2.h
index 426af0c9..5698157d 100644
--- a/filter-ewma2/ftnoir_filter_ewma2.h
+++ b/filter-ewma2/ftnoir_filter_ewma2.h
@@ -13,9 +13,9 @@ struct settings : opts {
value<slider_value> kMinSmoothing, kMaxSmoothing, kSmoothingScaleCurve;
settings() :
opts("ewma-filter"),
- kMinSmoothing(b, "min-smoothing", slider_value(.02, .01, 1)),
- kMaxSmoothing(b, "max-smoothing", slider_value(.7, .01, 1)),
- kSmoothingScaleCurve(b, "smoothing-scale-curve", slider_value(.8, .1, 5))
+ kMinSmoothing(b, "min-smoothing", { .02, .01, 1 }),
+ kMaxSmoothing(b, "max-smoothing", { .7, .01, 1 }),
+ kSmoothingScaleCurve(b, "smoothing-scale-curve", { .8, .1, 5 })
{}
};
@@ -28,15 +28,16 @@ public:
module_status initialize() override { return status_ok(); }
private:
// Deltas are smoothed over the last 1/60sec.
- const double delta_RC = 1./60;
+ static constexpr double delta_RC = 1./60;
// Noise is smoothed over the last 60sec.
- const double noise_RC = 60.0;
+ static constexpr double noise_RC_max = 60.0;
+ double noise_RC = 0.0;
double last_delta[6];
double last_noise[6];
double last_output[6];
Timer timer;
settings s;
- bool first_run;
+ bool first_run = true;
};
class dialog_ewma: public IFilterDialog
@@ -58,7 +59,8 @@ private slots:
class ewmaDll : public Metadata
{
-public:
- QString name() { return otr_tr("EWMA"); }
+ Q_OBJECT
+
+ QString name() { return tr("EWMA"); }
QIcon icon() { return QIcon(":/images/filter-16.png"); }
};
diff --git a/filter-ewma2/lang/de_DE.ts b/filter-ewma2/lang/de_DE.ts
new file mode 100644
index 00000000..be6712e6
--- /dev/null
+++ b/filter-ewma2/lang/de_DE.ts
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>UICdialog_ewma</name>
+ <message>
+ <source>EWMA filter settings</source>
+ <translation>EWMA-Filtereinstellungen</translation>
+ </message>
+ <message>
+ <source>Max</source>
+ <translation>Maximum</translation>
+ </message>
+ <message>
+ <source>Min</source>
+ <translation>Minimum</translation>
+ </message>
+ <message>
+ <source>Curve</source>
+ <translation>Kurve</translation>
+ </message>
+ <message>
+ <source>100%</source>
+ <translation>100%</translation>
+ </message>
+ <message>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; font-size:10pt; font-weight:600;&quot;&gt;Give the filter a few seconds to warm up and stop shaking.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Min:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Defines the way the filter responds to fast movements;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Higher value: slower response;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Max:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Defines the way the filter responds to slow movements;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Higher value: slower response;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Pow:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Defines the filters &apos;readiness&apos; to respond to speed changes;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Higher value = &lt;/span&gt;&lt;span style=&quot; font-size:10pt; font-weight:600;&quot;&gt;faster&lt;/span&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt; response;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; font-size:10pt; font-weight:600;&quot;&gt;Bitte einige Sekunden warten, bis der Filter warm gelaufen ist und nicht mehr zittert.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Minimum:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Definiert, wie der Filter auf schnelle Bewegungen reagiert;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Höherer Wert: langsameres Ansprechen;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Maximum:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Definiert, wie der Filter auf langsame Bewegungen reagiert;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Höherer Wert: langsameres Ansprechen;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Stärke:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Definiert die &apos;Bereitschaft&apos; des Filters, auf Geschwindigkeitsänderungen zu reagieren;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Höherer Wert = &lt;/span&gt;&lt;span style=&quot; font-size:10pt; font-weight:600;&quot;&gt;schnelleres&lt;/span&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt; Ansprechen;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>ewmaDll</name>
+ <message>
+ <source>EWMA</source>
+ <translation>EWMA</translation>
+ </message>
+</context>
+</TS>
diff --git a/filter-ewma2/lang/nl_NL.ts b/filter-ewma2/lang/nl_NL.ts
index 4ebfae5d..8c718b7d 100644
--- a/filter-ewma2/lang/nl_NL.ts
+++ b/filter-ewma2/lang/nl_NL.ts
@@ -28,7 +28,7 @@
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; font-size:10pt; font-weight:600;&quot;&gt;Give the filter up to 60 seconds to warm up and stop shaking.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; font-size:10pt; font-weight:600;&quot;&gt;Give the filter a few seconds to warm up and stop shaking.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Min:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Defines the way the filter responds to fast movements;&lt;/span&gt;&lt;/p&gt;
@@ -45,4 +45,11 @@ p, li { white-space: pre-wrap; }
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>ewmaDll</name>
+ <message>
+ <source>EWMA</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/filter-ewma2/lang/ru_RU.ts b/filter-ewma2/lang/ru_RU.ts
index b37f781d..14afb40e 100644
--- a/filter-ewma2/lang/ru_RU.ts
+++ b/filter-ewma2/lang/ru_RU.ts
@@ -28,7 +28,7 @@
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; font-size:10pt; font-weight:600;&quot;&gt;Give the filter up to 60 seconds to warm up and stop shaking.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; font-size:10pt; font-weight:600;&quot;&gt;Give the filter a few seconds to warm up and stop shaking.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Min:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Defines the way the filter responds to fast movements;&lt;/span&gt;&lt;/p&gt;
@@ -45,4 +45,11 @@ p, li { white-space: pre-wrap; }
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>ewmaDll</name>
+ <message>
+ <source>EWMA</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/filter-ewma2/lang/stub.ts b/filter-ewma2/lang/stub.ts
index 79e93591..fcce862e 100644
--- a/filter-ewma2/lang/stub.ts
+++ b/filter-ewma2/lang/stub.ts
@@ -28,7 +28,7 @@
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; font-size:10pt; font-weight:600;&quot;&gt;Give the filter up to 60 seconds to warm up and stop shaking.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; font-size:10pt; font-weight:600;&quot;&gt;Give the filter a few seconds to warm up and stop shaking.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Min:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Defines the way the filter responds to fast movements;&lt;/span&gt;&lt;/p&gt;
@@ -45,4 +45,11 @@ p, li { white-space: pre-wrap; }
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>ewmaDll</name>
+ <message>
+ <source>EWMA</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/filter-ewma2/lang/zh_CN.ts b/filter-ewma2/lang/zh_CN.ts
index 79e93591..b709f5c3 100644
--- a/filter-ewma2/lang/zh_CN.ts
+++ b/filter-ewma2/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>UICdialog_ewma</name>
<message>
@@ -28,7 +28,7 @@
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; font-size:10pt; font-weight:600;&quot;&gt;Give the filter up to 60 seconds to warm up and stop shaking.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; font-size:10pt; font-weight:600;&quot;&gt;Give the filter a few seconds to warm up and stop shaking.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Min:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Defines the way the filter responds to fast movements;&lt;/span&gt;&lt;/p&gt;
@@ -42,6 +42,30 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Defines the filters &apos;readiness&apos; to respond to speed changes;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Higher value = &lt;/span&gt;&lt;span style=&quot; font-size:10pt; font-weight:600;&quot;&gt;faster&lt;/span&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt; response;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; font-size:10pt; font-weight:600;&quot;&gt;给过滤器一点时间æ¥å®Œæˆé¢„热与消除抖动. &lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Min:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;定义滤波器对快速è¿åŠ¨çš„å“应方å¼; &lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;数值越大, å“应越慢; &lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Max:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;定义滤波器对慢速è¿åŠ¨çš„å“应方å¼; &lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;数值越大, å“应越慢; &lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Pow:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;定义滤波器&apos;readiness&apos; to respond to speed changes;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;更大数值 = &lt;/span&gt;&lt;span style=&quot; font-size:10pt; font-weight:600;&quot;&gt;æ›´å¿«&lt;/span&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt; å“应;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>ewmaDll</name>
+ <message>
+ <source>EWMA</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/filter-hamilton/CMakeLists.txt b/filter-hamilton/CMakeLists.txt
new file mode 100644
index 00000000..8d4d71b0
--- /dev/null
+++ b/filter-hamilton/CMakeLists.txt
@@ -0,0 +1 @@
+otr_module(filter-hamilton)
diff --git a/filter-hamilton/ReadMe.txt b/filter-hamilton/ReadMe.txt
new file mode 100644
index 00000000..bdd476da
--- /dev/null
+++ b/filter-hamilton/ReadMe.txt
@@ -0,0 +1,13 @@
+Add a new Hamilton filter.
+
+Hamilton Filter Key Features:
+- Instead of square, round (spherical) floating dead zones and smoothing areas are applied. Due to this, the angular size of these zones does not change when the Pitch angle changes. Diagonally rotations is as easy as moving along the Yaw and Pitch axes.
+- Rotations are not filtered by independent coordinates, but comprehensively, in 3D space. Rotations and movements are more natural. There are no view jumps at the borders of +/- 180 degrees.
+- The possibility of increasing the smoothing of rotations when zooming (when the head is approaching the monitor, that is, when increasing the -Z coordinate) is introduced. This makes it possible to more accurately aim and monitor remote targets.
+
+A full description of the Hamilton filter is available in Russian here:
+https://sites.google.com/site/diyheadtracking/home/opentrack/opentrack-hamilton-filter
+
+The Hamilton filter was tested by the Russian community, received positive reviews:
+https://forum.il2sturmovik.ru/topic/5061-opentrack-àêòóàëüíàÿ-èíôîðìàöèÿ-ïî-ïðîåêòó-ðåøåíèå-ïðîáëåì-âîïðîñû/page/24/
+https://forums.eagle.ru/showthread.php?t=23280&page=249
diff --git a/filter-hamilton/ftnoir_filter_hamilton.cpp b/filter-hamilton/ftnoir_filter_hamilton.cpp
new file mode 100644
index 00000000..c4429ecc
--- /dev/null
+++ b/filter-hamilton/ftnoir_filter_hamilton.cpp
@@ -0,0 +1,80 @@
+/* Copyright (c) 2020, GO63-samara <go1@list.ru>
+ *
+ * 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 "ftnoir_filter_hamilton.h"
+#include <cmath>
+#include <QMutexLocker>
+#include "api/plugin-api.hpp"
+#include "compat/hamilton-tools.h"
+
+hamilton::hamilton() = default;
+
+void hamilton::filter(const double *input, double *output)
+{
+ tQuat quat_input = QuatFromYPR( &input[Yaw] );
+
+ if (first_run)
+ {
+ first_run = false;
+ quat_last = quat_input;
+ pos_last = {input[TX], input[TY], input[TZ]};
+ for (int i=0; i<6; i++) output[i] = input[i];
+ return;
+ }
+
+ // positions:
+ const double pos_max {s.kMaxDist};
+ const double pos_deadzone{s.kDeadZoneDist};
+ const double pos_pow {s.kPowDist};
+
+ double dist = VectorDistance( &input[TX], pos_last);
+
+ double alpha = (dist - pos_deadzone) / (pos_max + pos_deadzone + EPSILON);
+ alpha = std::min(1.0, std::max(0.0, alpha));
+ if (alpha > 0.0)
+ alpha = pow(alpha, pos_pow);
+ // Scale alpha so that alpha * dist <= dist - pos_deadzone. This ensures that
+ // the center of the deadzone will never move closer to the input position than
+ // distance dist. And this ensures that the view never jumps ahead of head
+ // movements.
+ alpha *= (dist - pos_deadzone) / (dist + EPSILON);
+
+ pos_last = Lerp(pos_last, input, alpha);
+
+ output[TX] = pos_last.v[0];
+ output[TY] = pos_last.v[1];
+ output[TZ] = pos_last.v[2];
+
+ // zoom smoothing:
+ const double pow_zoom {s.kPowZoom};
+ const double max_z {s.kMaxZ};
+ double rot_zoom = pow_zoom;
+
+ if (output[TZ] > 0) rot_zoom = 0;
+ else rot_zoom *= -output[TZ] / (max_z + EPSILON);
+ rot_zoom = fmin( rot_zoom, pow_zoom );
+
+ // rotations:
+ const double rot_max {s.kMaxRot};
+ const double rot_pow {s.kPowRot};
+ const double rot_deadzone{s.kDeadZoneRot};
+
+ double angle = AngleBetween(quat_input, quat_last);
+
+ alpha = (angle - rot_deadzone) / (rot_max + rot_deadzone + EPSILON);
+ alpha = std::min(1.0, std::max(0.0, alpha));
+ if (alpha > 0.0)
+ alpha = pow(alpha, rot_pow + rot_zoom);
+ // see comment in earlier alpha calculation above
+ alpha *= (angle - rot_deadzone) / (angle + EPSILON);
+
+ quat_last = Slerp(quat_last, quat_input, alpha);
+
+ QuatToYPR(quat_last, &output[Yaw] );
+}
+
+OPENTRACK_DECLARE_FILTER(hamilton, dialog_hamilton, hamiltonDll)
diff --git a/filter-hamilton/ftnoir_filter_hamilton.h b/filter-hamilton/ftnoir_filter_hamilton.h
new file mode 100644
index 00000000..199eef80
--- /dev/null
+++ b/filter-hamilton/ftnoir_filter_hamilton.h
@@ -0,0 +1,74 @@
+/* Copyright (c) 2020, GO63-samara <go1@list.ru>
+ *
+ * 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_ftnoir_hamilton_filtercontrols.h"
+#include <QWidget>
+#include <QMutex>
+#include "options/options.hpp"
+//#include "compat/timer.hpp"
+#include "compat/hamilton-tools.h"
+
+using namespace options;
+
+struct settings : opts {
+ value<slider_value> kMaxRot, kPowRot, kDeadZoneRot,
+ kMaxDist, kPowDist, kDeadZoneDist,
+ kPowZoom, kMaxZ;
+ settings() :
+ opts ("hamilton-filter"),
+ kMaxRot (b, "max-radius-smoothing", { .01, .001, 25.0 }),
+ kPowRot (b, "smoothing-power-rot", { .01, .001, 4.0 }),
+ kDeadZoneRot (b, "dead-zone-radius-rot", { .01, .001, 0.5 }),
+ kMaxDist (b, "max-distance-smoothing",{ .01, .001, 20.0 }),
+ kPowDist (b, "smoothing-power-dist", { .01, .001, 4.0 }),
+ kDeadZoneDist(b, "dead-zone-radius-dist", { .01, .001, 0.5 }),
+ kPowZoom (b, "smoothing-power-zoom", { .01, .001, 4.0 }),
+ kMaxZ (b, "max-z", { .01, .001, 100.0 })
+ {}
+};
+
+class hamilton : public IFilter
+{
+public:
+ hamilton();
+ void filter(const double *input, double *output) override;
+ void center() override { first_run = true; }
+ module_status initialize() override { return status_ok(); }
+private:
+ tQuat quat_last;
+ tVector pos_last;
+ settings s;
+ bool first_run = true;
+};
+
+class dialog_hamilton: public IFilterDialog
+{
+ Q_OBJECT
+public:
+ dialog_hamilton();
+ void register_filter(IFilter*) override {}
+ void unregister_filter() override {}
+
+private:
+ Ui::UICdialog_hamilton ui;
+ settings s;
+
+private slots:
+ void doOK();
+ void doCancel();
+};
+
+class hamiltonDll : public Metadata
+{
+ Q_OBJECT
+
+ QString name() { return tr("Hamilton"); }
+ QIcon icon() { return QIcon(":/images/filter-16.png"); }
+};
diff --git a/filter-hamilton/ftnoir_filter_hamilton_dialog.cpp b/filter-hamilton/ftnoir_filter_hamilton_dialog.cpp
new file mode 100644
index 00000000..11f4c067
--- /dev/null
+++ b/filter-hamilton/ftnoir_filter_hamilton_dialog.cpp
@@ -0,0 +1,64 @@
+/* Copyright (c) 2020, GO63-samara <go1@list.ru>
+ *
+ * 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 "ftnoir_filter_hamilton.h"
+#include <cmath>
+#include <QDebug>
+#include <QString>
+#include "api/plugin-api.hpp"
+#include "ui_ftnoir_hamilton_filtercontrols.h"
+
+dialog_hamilton::dialog_hamilton()
+{
+ ui.setupUi(this);
+
+ connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
+ connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
+
+ tie_setting(s.kMaxRot, ui.maxRot);
+ tie_setting(s.kMaxRot, ui.lbMaxRot, [](double x)
+ { return QStringLiteral("%1\xB0").arg(x, 0, 'f', 2);});
+
+ tie_setting(s.kPowRot, ui.powRot);
+ tie_setting(s.kPowRot, ui.lbPowRot, [](double x)
+ { return QStringLiteral("%1").arg(x, 0, 'f', 2);});
+
+ tie_setting(s.kDeadZoneRot, ui.dzRot);
+ tie_setting(s.kDeadZoneRot, ui.lbDZRot, [](double x)
+ { return QStringLiteral("%1\xB0").arg(x, 0, 'f', 2);});
+
+ tie_setting(s.kPowZoom, ui.powZoom);
+ tie_setting(s.kPowZoom, ui.lbPowZoom, [](double x)
+ { return QStringLiteral("%1").arg(x, 0, 'f', 2);});
+
+ tie_setting(s.kMaxZ, ui.maxZ);
+ tie_setting(s.kMaxZ, ui.lbMaxZ, [](double x)
+ { return QStringLiteral("%1").arg(x, 0, 'f', 2);});
+
+ tie_setting(s.kMaxDist, ui.maxDist);
+ tie_setting(s.kMaxDist, ui.lbMaxDist, [](double x)
+ { return QStringLiteral("%1cm").arg(x, 0, 'f', 2);});
+
+ tie_setting(s.kPowDist, ui.powDist);
+ tie_setting(s.kPowDist, ui.lbPowDist, [](double x)
+ { return QStringLiteral("%1").arg(x, 0, 'f', 2);});
+
+ tie_setting(s.kDeadZoneDist, ui.dzDist);
+ tie_setting(s.kDeadZoneDist, ui.lbDZDist, [](double x)
+ { return QStringLiteral("%1cm").arg(x, 0, 'f', 2);});
+}
+
+void dialog_hamilton::doOK()
+{
+ s.b->save();
+ close();
+}
+
+void dialog_hamilton::doCancel()
+{
+ close();
+}
diff --git a/filter-hamilton/ftnoir_hamilton_filtercontrols.ui b/filter-hamilton/ftnoir_hamilton_filtercontrols.ui
new file mode 100644
index 00000000..4c8b1536
--- /dev/null
+++ b/filter-hamilton/ftnoir_hamilton_filtercontrols.ui
@@ -0,0 +1,819 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UICdialog_hamilton</class>
+ <widget class="QWidget" name="UICdialog_hamilton">
+ <property name="windowModality">
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>514</width>
+ <height>563</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>485</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>50</weight>
+ <bold>false</bold>
+ </font>
+ </property>
+ <property name="windowTitle">
+ <string>Hamilton filter settings</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../gui/opentrack-res.qrc">
+ <normaloff>:/images/filter-16.png</normaloff>:/images/filter-16.png</iconset>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="2" column="0">
+ <widget class="QGroupBox" name="groupBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>496</width>
+ <height>150</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Rotations: </string>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="8" column="2">
+ <widget class="QSlider" name="powRot">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>400</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="pageStep">
+ <number>50</number>
+ </property>
+ <property name="value">
+ <number>200</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBothSides</enum>
+ </property>
+ <property name="tickInterval">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="3">
+ <widget class="QLabel" name="lbPowRot">
+ <property name="minimumSize">
+ <size>
+ <width>45</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>2,00</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="2">
+ <widget class="QSlider" name="dzRot">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>50</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="pageStep">
+ <number>5</number>
+ </property>
+ <property name="value">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBothSides</enum>
+ </property>
+ <property name="tickInterval">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
+ <widget class="QLabel" name="lbRpow">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>Smoothing power:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="lbRdz">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>Dead Zone:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="3">
+ <widget class="QLabel" name="lbDZRot">
+ <property name="minimumSize">
+ <size>
+ <width>45</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>0,01</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QLabel" name="lbRmax">
+ <property name="text">
+ <string>Max distance:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="2">
+ <widget class="QSlider" name="maxRot">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>250</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="pageStep">
+ <number>50</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBothSides</enum>
+ </property>
+ <property name="tickInterval">
+ <number>50</number>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="3">
+ <widget class="QLabel" name="lbMaxRot">
+ <property name="minimumSize">
+ <size>
+ <width>45</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>10,00</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ <property name="centerButtons">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>For both rotations and positions: No movement occurs within the dead zone. Smoothing scales down to minimum at max distance + 2 x dead zone.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>150</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Positions: </string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="1" column="2">
+ <widget class="QLabel" name="lbMaxDist">
+ <property name="minimumSize">
+ <size>
+ <width>45</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>10,00</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="lbRpow_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>Smoothing power:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lbDmax">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>Max distance:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSlider" name="maxDist">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>200</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="pageStep">
+ <number>50</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBothSides</enum>
+ </property>
+ <property name="tickInterval">
+ <number>50</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSlider" name="powDist">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>400</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="pageStep">
+ <number>50</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBothSides</enum>
+ </property>
+ <property name="tickInterval">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="lbPowDist">
+ <property name="minimumSize">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>1,00</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="lbRdz_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>Dead Zone:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSlider" name="dzDist">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>50</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="pageStep">
+ <number>10</number>
+ </property>
+ <property name="value">
+ <number>2</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBothSides</enum>
+ </property>
+ <property name="tickInterval">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="lbDZDist">
+ <property name="minimumSize">
+ <size>
+ <width>45</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>0,02</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Zoom smoothing: </string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="2" column="0">
+ <widget class="QLabel" name="lbZpow">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>Smoothing power:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="lbPowZoom">
+ <property name="minimumSize">
+ <size>
+ <width>45</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>2,00</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSlider" name="powZoom">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>400</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="pageStep">
+ <number>50</number>
+ </property>
+ <property name="value">
+ <number>200</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBothSides</enum>
+ </property>
+ <property name="tickInterval">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lbZpow_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>Max Z:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSlider" name="maxZ">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>1000</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="pageStep">
+ <number>50</number>
+ </property>
+ <property name="value">
+ <number>150</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBothSides</enum>
+ </property>
+ <property name="tickInterval">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="lbMaxZ">
+ <property name="minimumSize">
+ <size>
+ <width>45</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>15,00</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>When you lean forward, zoom smoothing increases the smoothing power for rotations. Maximum additonal smoothing power occurs when you lean forward by a distance of Max Z.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>dzRot</tabstop>
+ <tabstop>maxRot</tabstop>
+ <tabstop>powRot</tabstop>
+ <tabstop>dzDist</tabstop>
+ <tabstop>maxDist</tabstop>
+ <tabstop>powDist</tabstop>
+ <tabstop>maxZ</tabstop>
+ <tabstop>powZoom</tabstop>
+ </tabstops>
+ <resources>
+ <include location="../gui/opentrack-res.qrc"/>
+ </resources>
+ <connections/>
+ <slots>
+ <slot>startEngineClicked()</slot>
+ <slot>stopEngineClicked()</slot>
+ <slot>cameraSettingsClicked()</slot>
+ </slots>
+</ui>
diff --git a/filter-hamilton/lang/de_DE.ts b/filter-hamilton/lang/de_DE.ts
new file mode 100644
index 00000000..6ea1f913
--- /dev/null
+++ b/filter-hamilton/lang/de_DE.ts
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>UICdialog_hamilton</name>
+ <message>
+ <source>Hamilton filter settings</source>
+ <translation>Hamilton-Filtereinstellungen</translation>
+ </message>
+ <message>
+ <source>Rotations: </source>
+ <translation>Rotationen: </translation>
+ </message>
+ <message>
+ <source>2,00</source>
+ <translation>2,00</translation>
+ </message>
+ <message>
+ <source>Smoothing power:</source>
+ <translation>Glättungsstärke:</translation>
+ </message>
+ <message>
+ <source>Dead Zone:</source>
+ <translation>Totbereich:</translation>
+ </message>
+ <message>
+ <source>0,01</source>
+ <translation>0,01</translation>
+ </message>
+ <message>
+ <source>Max distance:</source>
+ <translation>Maximale Distanz:</translation>
+ </message>
+ <message>
+ <source>10,00</source>
+ <translation>10,00</translation>
+ </message>
+ <message>
+ <source>For both rotations and positions: No movement occurs within the dead zone. Smoothing scales down to minimum at max distance + 2 x dead zone.</source>
+ <translation>Für alle Rotationen und Positionen: Es findet keine Bewegung innerhalb des Totbereichs statt. Die Glättung skaliert bis zum Minimum an der maximalen Distanz + 2x der Totbereich.</translation>
+ </message>
+ <message>
+ <source>Positions: </source>
+ <translation>Positionen: </translation>
+ </message>
+ <message>
+ <source>1,00</source>
+ <translation>1,00</translation>
+ </message>
+ <message>
+ <source>0,02</source>
+ <translation>0,02</translation>
+ </message>
+ <message>
+ <source>Zoom smoothing: </source>
+ <translation>Zoom-Glättung: </translation>
+ </message>
+ <message>
+ <source>Max Z:</source>
+ <translation>Maximales Z:</translation>
+ </message>
+ <message>
+ <source>15,00</source>
+ <translation>15,00</translation>
+ </message>
+ <message>
+ <source>When you lean forward, zoom smoothing increases the smoothing power for rotations. Maximum additonal smoothing power occurs when you lean forward by a distance of Max Z.</source>
+ <translation>Beim Vorbeugen erhöht die Zoom-Glättung die Glättungsstärke für Rotationen. Die maximal zusätzliche Glättungsstärke tritt auf beim Vorbeugen mit der Distanz Max Z.</translation>
+ </message>
+</context>
+<context>
+ <name>hamiltonDll</name>
+ <message>
+ <source>Hamilton</source>
+ <translation>Hamilton</translation>
+ </message>
+</context>
+</TS>
diff --git a/filter-hamilton/lang/nl_NL.ts b/filter-hamilton/lang/nl_NL.ts
new file mode 100644
index 00000000..1c720540
--- /dev/null
+++ b/filter-hamilton/lang/nl_NL.ts
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>UICdialog_hamilton</name>
+ <message>
+ <source>Rotations: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Positions: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Zoom smoothing: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hamilton filter settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max Z:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>10,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0,01</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>2,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>1,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0,02</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>15,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Smoothing power:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dead Zone:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max distance:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>For both rotations and positions: No movement occurs within the dead zone. Smoothing scales down to minimum at max distance + 2 x dead zone.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>When you lean forward, zoom smoothing increases the smoothing power for rotations. Maximum additonal smoothing power occurs when you lean forward by a distance of Max Z.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>hamiltonDll</name>
+ <message>
+ <source>Hamilton</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/filter-hamilton/lang/ru_RU.ts b/filter-hamilton/lang/ru_RU.ts
new file mode 100644
index 00000000..f0681b21
--- /dev/null
+++ b/filter-hamilton/lang/ru_RU.ts
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>UICdialog_hamilton</name>
+ <message>
+ <source>Rotations: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Positions: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Zoom smoothing: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hamilton filter settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max Z:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>10,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0,01</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>2,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>1,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0,02</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>15,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Smoothing power:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dead Zone:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max distance:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>For both rotations and positions: No movement occurs within the dead zone. Smoothing scales down to minimum at max distance + 2 x dead zone.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>When you lean forward, zoom smoothing increases the smoothing power for rotations. Maximum additonal smoothing power occurs when you lean forward by a distance of Max Z.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>hamiltonDll</name>
+ <message>
+ <source>Hamilton</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/filter-hamilton/lang/stub.ts b/filter-hamilton/lang/stub.ts
new file mode 100644
index 00000000..2b767312
--- /dev/null
+++ b/filter-hamilton/lang/stub.ts
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>UICdialog_hamilton</name>
+ <message>
+ <source>Rotations: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Positions: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Zoom smoothing: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hamilton filter settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max Z:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>10,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0,01</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>2,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>1,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0,02</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>15,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Smoothing power:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dead Zone:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max distance:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>For both rotations and positions: No movement occurs within the dead zone. Smoothing scales down to minimum at max distance + 2 x dead zone.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>When you lean forward, zoom smoothing increases the smoothing power for rotations. Maximum additonal smoothing power occurs when you lean forward by a distance of Max Z.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>hamiltonDll</name>
+ <message>
+ <source>Hamilton</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/filter-hamilton/lang/zh_CN.ts b/filter-hamilton/lang/zh_CN.ts
new file mode 100644
index 00000000..a299023d
--- /dev/null
+++ b/filter-hamilton/lang/zh_CN.ts
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>UICdialog_hamilton</name>
+ <message>
+ <source>Rotations: </source>
+ <translation type="unfinished">旋转: </translation>
+ </message>
+ <message>
+ <source>Positions: </source>
+ <translation type="unfinished">ä½ç½®: </translation>
+ </message>
+ <message>
+ <source>Zoom smoothing: </source>
+ <translation type="unfinished">缩放时平滑: </translation>
+ </message>
+ <message>
+ <source>Hamilton filter settings</source>
+ <translation>Hamilton 过滤器设置</translation>
+ </message>
+ <message>
+ <source>Max Z:</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>10,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0,01</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>2,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>1,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>0,02</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>15,00</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Smoothing power:</source>
+ <translation>平滑强度:</translation>
+ </message>
+ <message>
+ <source>Dead Zone:</source>
+ <translation>死区:</translation>
+ </message>
+ <message>
+ <source>Max distance:</source>
+ <translation type="unfinished">最大è·ç¦»:</translation>
+ </message>
+ <message>
+ <source>For both rotations and positions: No movement occurs within the dead zone. Smoothing scales down to minimum at max distance + 2 x dead zone.</source>
+ <translation type="unfinished">旋转和ä½ç½®åœ¨æ­»åŒºèŒƒå›´å†…都ä¸ä¼šå‘生移动. 在最大è·ç¦» +2x死区时, smoothing缩å°è‡³æœ€å°å€¼.&lt;a href=&quot;https://github.com/opentrack/opentrack/pull/1667&quot;&gt;阅读此处Pull#1167&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <source>When you lean forward, zoom smoothing increases the smoothing power for rotations. Maximum additonal smoothing power occurs when you lean forward by a distance of Max Z.</source>
+ <translation type="unfinished">当您å‘å‰å€¾æ—¶, &quot;缩放时平滑&quot; 功能会增强旋转的平滑强度. å‰å€¾åˆ° Max Z è·ç¦»æ—¶, 增加的平滑强度最大.</translation>
+ </message>
+</context>
+<context>
+ <name>hamiltonDll</name>
+ <message>
+ <source>Hamilton</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/filter-kalman/CMakeLists.txt b/filter-kalman/CMakeLists.txt
deleted file mode 100644
index b6f8100e..00000000
--- a/filter-kalman/CMakeLists.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-find_package(Eigen3 QUIET)
-if(EIGEN3_FOUND)
- otr_module(filter-kalman)
- target_include_directories(opentrack-filter-kalman SYSTEM PUBLIC ${EIGEN3_INCLUDE_DIR})
-endif()
diff --git a/filter-kalman/ftnoir_kalman_filtercontrols.ui b/filter-kalman/ftnoir_kalman_filtercontrols.ui
deleted file mode 100644
index 83ee8e96..00000000
--- a/filter-kalman/ftnoir_kalman_filtercontrols.ui
+++ /dev/null
@@ -1,170 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>KalmanUICdialog_kalman</class>
- <widget class="QWidget" name="KalmanUICdialog_kalman">
- <property name="windowModality">
- <enum>Qt::NonModal</enum>
- </property>
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>438</width>
- <height>141</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="windowTitle">
- <string>Kalman settings</string>
- </property>
- <property name="windowIcon">
- <iconset resource="../gui/opentrack-res.qrc">
- <normaloff>:/images/filter-16.png</normaloff>:/images/filter-16.png</iconset>
- </property>
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="autoFillBackground">
- <bool>false</bool>
- </property>
- <property name="styleSheet">
- <string notr="true"/>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QGroupBox" name="groupBox_2">
- <property name="title">
- <string>Measurement noise</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Rotation</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QSlider" name="noiseRotSlider">
- <property name="maximum">
- <number>400</number>
- </property>
- <property name="singleStep">
- <number>1</number>
- </property>
- <property name="pageStep">
- <number>100</number>
- </property>
- <property name="tracking">
- <bool>true</bool>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="invertedAppearance">
- <bool>false</bool>
- </property>
- <property name="tickPosition">
- <enum>QSlider::TicksBelow</enum>
- </property>
- <property name="tickInterval">
- <number>100</number>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>Position</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QSlider" name="noisePosSlider">
- <property name="maximum">
- <number>400</number>
- </property>
- <property name="pageStep">
- <number>100</number>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="tickPosition">
- <enum>QSlider::TicksBelow</enum>
- </property>
- <property name="tickInterval">
- <number>100</number>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QLabel" name="noiseRotLabel">
- <property name="minimumSize">
- <size>
- <width>65</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>°</string>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QLabel" name="noisePosLabel">
- <property name="minimumSize">
- <size>
- <width>65</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>-</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <resources>
- <include location="../gui/opentrack-res.qrc"/>
- </resources>
- <connections/>
- <designerdata>
- <property name="gridDeltaX">
- <number>10</number>
- </property>
- <property name="gridDeltaY">
- <number>10</number>
- </property>
- <property name="gridSnapX">
- <bool>false</bool>
- </property>
- <property name="gridSnapY">
- <bool>false</bool>
- </property>
- <property name="gridVisible">
- <bool>true</bool>
- </property>
- </designerdata>
- <slots>
- <slot>startEngineClicked()</slot>
- <slot>stopEngineClicked()</slot>
- <slot>cameraSettingsClicked()</slot>
- </slots>
-</ui>
diff --git a/filter-kalman/kalman.cpp b/filter-kalman/kalman.cpp
deleted file mode 100644
index 3a0da608..00000000
--- a/filter-kalman/kalman.cpp
+++ /dev/null
@@ -1,297 +0,0 @@
-/* Copyright (c) 2016 Michael Welter <mw.pub@welter-4d.de>
- *
- * 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 "kalman.h"
-#include <QDebug>
-#include <cmath>
-
-void KalmanFilter::init()
-{
- // allocate and initialize matrices
- measurement_noise_cov = MeasureMatrix::Zero();
- process_noise_cov = StateMatrix::Zero();
- state_cov = StateMatrix::Zero();
- state_cov_prior = StateMatrix::Zero();
- transition_matrix = StateMatrix::Zero();
- measurement_matrix = StateToMeasureMatrix::Zero();
- kalman_gain = MeasureToStateMatrix::Zero();
- // initialize state variables
- state = StateVector::Zero();
- state_prior = StateVector::Zero();
- innovation = PoseVector::Zero();
-}
-
-
-void KalmanFilter::time_update()
-{
- state_prior = transition_matrix * state;
- state_cov_prior = transition_matrix * state_cov * transition_matrix.transpose() + process_noise_cov;
-}
-
-
-void KalmanFilter::measurement_update(const PoseVector &measurement)
-{
- MeasureMatrix tmp = measurement_matrix * state_cov_prior * measurement_matrix.transpose() + measurement_noise_cov;
- MeasureMatrix tmp_inv = tmp.inverse();
- kalman_gain = state_cov_prior * measurement_matrix.transpose() * tmp_inv;
- innovation = measurement - measurement_matrix * state_prior;
- state = state_prior + kalman_gain * innovation;
- state_cov = state_cov_prior - kalman_gain * measurement_matrix * state_cov_prior;
-}
-
-
-
-void KalmanProcessNoiseScaler::init()
-{
- base_cov = StateMatrix::Zero(NUM_STATE_DOF, NUM_STATE_DOF);
- innovation_cov_estimate = MeasureMatrix::Zero(NUM_MEASUREMENT_DOF, NUM_MEASUREMENT_DOF);
-}
-
-
-/* Uses
- innovation, measurement_matrix, measurement_noise_cov, and state_cov_prior
- found in KalmanFilter. It sets
- process_noise_cov
-*/
-void KalmanProcessNoiseScaler::update(KalmanFilter &kf, double dt)
-{
- MeasureMatrix ddT = kf.innovation * kf.innovation.transpose();
- double f = dt / (dt + settings::adaptivity_window_length);
- innovation_cov_estimate =
- f * ddT + (1. - f) * innovation_cov_estimate;
-
- double T1 = (innovation_cov_estimate - kf.measurement_noise_cov).trace();
- double T2 = (kf.measurement_matrix * kf.state_cov_prior * kf.measurement_matrix.transpose()).trace();
- double alpha = 0.001;
- if (T2 > 0. && T1 > 0.)
- {
- alpha = T1 / T2;
- alpha = std::sqrt(alpha);
- alpha = std::fmin(1000., std::fmax(0.001, alpha));
- }
- kf.process_noise_cov = alpha * base_cov;
- //qDebug() << "alpha = " << alpha;
-}
-
-
-PoseVector DeadzoneFilter::filter(const PoseVector &input)
-{
- PoseVector out;
- for (int i = 0; i < input.rows(); ++i)
- {
- const double dz = dz_size[i];
- if (dz > 0.)
- {
- const double delta = input[i] - last_output[i];
- const double f = std::pow(std::fabs(delta) / dz, settings::deadzone_exponent);
- const double response = f / (f + 1.) * delta;
- out[i] = last_output[i] + response;
- }
- else
- out[i] = input[i];
- last_output[i] = out[i];
- }
- return out;
-}
-
-
-void kalman::fill_transition_matrix(double dt)
-{
- for (int i = 0; i < 6; ++i)
- {
- kf.transition_matrix(i, i + 6) = dt;
- }
-}
-
-void kalman::fill_process_noise_cov_matrix(StateMatrix &target, double dt) const
-{
- // This model is like movement at fixed velocity plus superimposed
- // brownian motion. Unlike standard models for tracking of objects
- // with a very well predictable trajectory (e.g.
- // https://en.wikipedia.org/wiki/Kalman_filter#Example_application.2C_technical)
- double sigma_pos = s.process_sigma_pos;
- double sigma_angle = s.process_sigma_rot;
- double a_pos = sigma_pos * sigma_pos * dt;
- double a_ang = sigma_angle * sigma_angle * dt;
- constexpr double b = 20;
- constexpr double c = 1.;
- for (int i = 0; i < 3; ++i)
- {
- target(i, i) = a_pos;
- target(i, i + 6) = a_pos * c;
- target(i + 6, i) = a_pos * c;
- target(i + 6, i + 6) = a_pos * b;
- }
- for (int i = 3; i < 6; ++i)
- {
- target(i, i) = a_ang;
- target(i, i + 6) = a_ang * c;
- target(i + 6, i) = a_ang * c;
- target(i + 6, i + 6) = a_ang * b;
- }
-}
-
-
-PoseVector kalman::do_kalman_filter(const PoseVector &input, double dt, bool new_input)
-{
- if (new_input)
- {
- dt = dt_since_last_input;
- fill_transition_matrix(dt);
- fill_process_noise_cov_matrix(kf_adaptive_process_noise_cov.base_cov, dt);
- kf_adaptive_process_noise_cov.update(kf, dt);
- kf.time_update();
- kf.measurement_update(input);
- }
- return kf.state.head(6);
-}
-
-
-
-kalman::kalman() {
- reset();
-}
-
-// The original code was written by Donovan Baarda <abo@minkirri.apana.org.au>
-// https://sourceforge.net/p/facetracknoir/discussion/1150909/thread/418615e1/?limit=25#af75/084b
-void kalman::reset()
-{
- kf.init();
- kf_adaptive_process_noise_cov.init();
- for (int i = 0; i < 6; ++i)
- {
- // initialize part of the transition matrix that do not change.
- kf.transition_matrix(i, i) = 1.;
- kf.transition_matrix(i + 6, i + 6) = 1.;
- // "extract" positions, i.e. the first 6 state dof.
- kf.measurement_matrix(i, i) = 1.;
- }
-
- double noise_variance_position = settings::map_slider_value(s.noise_pos_slider_value);
- double noise_variance_angle = settings::map_slider_value(s.noise_rot_slider_value);
- for (int i = 0; i < 3; ++i)
- {
- kf.measurement_noise_cov(i , i ) = noise_variance_position;
- kf.measurement_noise_cov(i + 3, i + 3) = noise_variance_angle;
- }
-
- fill_transition_matrix(0.03);
- fill_process_noise_cov_matrix(kf_adaptive_process_noise_cov.base_cov, 0.03);
-
- kf.process_noise_cov = kf_adaptive_process_noise_cov.base_cov;
- kf.state_cov = kf.process_noise_cov;
-
- for (int i = 0; i < 6; i++) {
- last_input[i] = 0;
- }
- first_run = true;
- dt_since_last_input = 0;
-
- prev_slider_pos[0] = static_cast<slider_value>(s.noise_pos_slider_value);
- prev_slider_pos[1] = static_cast<slider_value>(s.noise_rot_slider_value);
-
- dz_filter.reset();
-}
-
-
-void kalman::filter(const double* input_, double *output_)
-{
- // almost non-existent cost, so might as well ...
- Eigen::Map<const PoseVector> input(input_, PoseVector::RowsAtCompileTime, 1);
- Eigen::Map<PoseVector> output(output_, PoseVector::RowsAtCompileTime, 1);
-
- if (!(prev_slider_pos[0] == s.noise_pos_slider_value &&
- prev_slider_pos[1] == s.noise_rot_slider_value))
- {
- reset();
- }
-
- // Start the timer on first filter evaluation.
- if (first_run)
- {
- timer.start();
- first_run = false;
- return;
- }
-
- // Note this is a terrible way to detect when there is a new
- // frame of tracker input, but it is the best we have.
- bool new_input = input.cwiseNotEqual(last_input).any();
-
- // Get the time in seconds since last run and restart the timer.
- const double dt = timer.elapsed_seconds();
- dt_since_last_input += dt;
- timer.start();
-
- output = do_kalman_filter(input, dt, new_input);
-
- {
- // Compute deadzone size base on estimated state variance.
- // Given a constant input plus measurement noise, KF should converge to the true input.
- // This works well. That is the output pose becomes very still afte some time.
- // The QScaling adaptive filter makes the state cov vary depending on the estimated noise
- // and the measured noise of the innovation sequence. After a sudden movement it peaks
- // and then decays asymptotically to some constant value taken in stationary state.
- // We can use this to calculate the size of the deadzone, so that in the stationary state the
- // deadzone size is small. Thus the tracking error due to the dz-filter becomes also small.
- PoseVector variance = kf.state_cov.diagonal().head(6);
- dz_filter.dz_size = variance.cwiseSqrt() * s.deadzone_scale;
- }
- output = dz_filter.filter(output);
-
- if (new_input)
- {
- dt_since_last_input = 0;
- last_input = input;
- }
-}
-
-
-
-dialog_kalman::dialog_kalman()
- : filter(nullptr)
-{
- ui.setupUi(this);
- connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
- connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
-
- tie_setting(s.noise_rot_slider_value, ui.noiseRotSlider);
- tie_setting(s.noise_pos_slider_value, ui.noisePosSlider);
-
- connect(&s.noise_rot_slider_value, SIGNAL(valueChanged(const slider_value&)), this, SLOT(updateLabels(const slider_value&)));
- connect(&s.noise_pos_slider_value, SIGNAL(valueChanged(const slider_value&)), this, SLOT(updateLabels(const slider_value&)));
-
- updateLabels(slider_value());
-}
-
-
-void dialog_kalman::updateLabels(const slider_value&)
-{
- // M$ hates unicode! (M$ autoconverts source code of one kind of utf-8 format,
- // the one without BOM, to another kind that QT does not like)
- // Previous attempt to use c++11 utf8 strings like u8" °" now failed for unknown
- // reasons where it worked before. Hence fallback to QChar(0x00b0).
- this->ui.noiseRotLabel->setText(
- QString::number(settings::map_slider_value(s.noise_rot_slider_value), 'f', 3) + " " + QChar(0x00b0));
-
- this->ui.noisePosLabel->setText(
- QString::number(settings::map_slider_value(s.noise_pos_slider_value), 'f', 3) + " cm");
-}
-
-
-void dialog_kalman::doOK() {
- s.b->save();
- close();
-}
-
-
-void dialog_kalman::doCancel()
-{
- close();
-}
-
-
-OPENTRACK_DECLARE_FILTER(kalman, dialog_kalman, kalmanDll)
diff --git a/filter-kalman/kalman.h b/filter-kalman/kalman.h
deleted file mode 100644
index 431041dc..00000000
--- a/filter-kalman/kalman.h
+++ /dev/null
@@ -1,179 +0,0 @@
-#pragma once
-/* Copyright (c) 2016 Michael Welter <mw.pub@welter-4d.de>
- *
- * 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 "ui_ftnoir_kalman_filtercontrols.h"
-#include "api/plugin-api.hpp"
-#include "options/options.hpp"
-using namespace options;
-#include "compat/timer.hpp"
-
-#include <Eigen/Core>
-#include <Eigen/LU>
-
-#include <QString>
-#include <QWidget>
-
-#include <atomic>
-
-static constexpr inline int NUM_STATE_DOF = 12;
-static constexpr inline int NUM_MEASUREMENT_DOF = 6;
-// These vectors are compile time fixed size, stack allocated
-using StateToMeasureMatrix = Eigen::Matrix<double, NUM_MEASUREMENT_DOF, NUM_STATE_DOF>;
-using StateMatrix = Eigen::Matrix<double, NUM_STATE_DOF, NUM_STATE_DOF>;
-using MeasureToStateMatrix = Eigen::Matrix<double, NUM_STATE_DOF, NUM_MEASUREMENT_DOF>;
-using MeasureMatrix = Eigen::Matrix<double, NUM_MEASUREMENT_DOF, NUM_MEASUREMENT_DOF>;
-using StateVector = Eigen::Matrix<double, NUM_STATE_DOF, 1>;
-using PoseVector = Eigen::Matrix<double, NUM_MEASUREMENT_DOF, 1>;
-
-struct KalmanFilter
-{
- MeasureMatrix
- measurement_noise_cov;
- StateMatrix
- process_noise_cov,
- state_cov,
- state_cov_prior,
- transition_matrix;
- MeasureToStateMatrix
- kalman_gain;
- StateToMeasureMatrix
- measurement_matrix;
- StateVector
- state,
- state_prior;
- PoseVector
- innovation;
- void init();
- void time_update();
- void measurement_update(const PoseVector &measurement);
-
- EIGEN_MAKE_ALIGNED_OPERATOR_NEW
-};
-
-struct KalmanProcessNoiseScaler
-{
- MeasureMatrix
- innovation_cov_estimate;
- StateMatrix
- base_cov; // baseline (unscaled) process noise covariance matrix
- void init();
- void update(KalmanFilter &kf, double dt);
-
- EIGEN_MAKE_ALIGNED_OPERATOR_NEW
-};
-
-
-struct DeadzoneFilter
-{
- PoseVector
- last_output,
- dz_size;
- DeadzoneFilter() :
- last_output(PoseVector::Zero()),
- dz_size(PoseVector::Zero())
- {}
- void reset() {
- last_output = PoseVector::Zero();
- }
- PoseVector filter(const PoseVector &input);
-
- EIGEN_MAKE_ALIGNED_OPERATOR_NEW
-};
-
-
-struct settings : opts {
- value<slider_value> noise_rot_slider_value;
- value<slider_value> noise_pos_slider_value;
-
- static constexpr inline double adaptivity_window_length = 0.25; // seconds
- static constexpr inline double deadzone_scale = 8;
- static constexpr inline double deadzone_exponent = 2.0;
- static constexpr inline double process_sigma_pos = 0.5;
- static constexpr inline double process_sigma_rot = 0.5;
-
- static double map_slider_value(const slider_value &v_)
- {
- const double v = v_;
-#if 0
- //return std::pow(10., v * 4. - 3.);
-#else
- constexpr int min_log10 = -3;
- constexpr int max_log10 = 1;
- constexpr int num_divisions = max_log10 - min_log10;
- /* ascii art representation of slider
- // ----- // ------// ------// ------- // 4 divisions
- -3 - 2 -1 0 1 power of 10
- | |
- | f + left_side_log10
- |
- left_side_log10
- */
- const int k = v * num_divisions; // in which division are we?!
- const double f = v * num_divisions - k; // where in the division are we?!
- const double ff = f * 9. + 1.;
- const double multiplier = int(ff * 10.) / 10.;
- const int left_side_log10 = min_log10 + k;
- const double val = std::pow(10., left_side_log10) * multiplier;
- return val;
-#endif
- }
-
- settings() :
- opts("kalman-filter"),
- noise_rot_slider_value(b, "noise-rotation-slider", slider_value(0.5, 0., 1.)),
- noise_pos_slider_value(b, "noise-position-slider", slider_value(0.5, 0., 1.))
- {}
-
-};
-
-class kalman : public IFilter
-{
- PoseVector do_kalman_filter(const PoseVector &input, double dt, bool new_input);
- void fill_transition_matrix(double dt);
- void fill_process_noise_cov_matrix(StateMatrix &target, double dt) const;
-public:
- kalman();
- void reset();
- void filter(const double *input, double *output) override;
- void center() override { reset(); }
- module_status initialize() { return status_ok(); }
- PoseVector last_input;
- Timer timer;
- bool first_run;
- double dt_since_last_input;
- settings s;
- KalmanFilter kf;
- KalmanProcessNoiseScaler kf_adaptive_process_noise_cov;
- DeadzoneFilter dz_filter;
- slider_value prev_slider_pos[2];
-
- EIGEN_MAKE_ALIGNED_OPERATOR_NEW
-};
-
-class kalmanDll : public Metadata
-{
-public:
- QString name() { return otr_tr("Kalman"); }
- QIcon icon() { return QIcon(":/images/filter-16.png"); }
-};
-
-class dialog_kalman: public IFilterDialog
-{
- Q_OBJECT
-public:
- dialog_kalman();
- Ui::KalmanUICdialog_kalman ui;
- void register_filter(IFilter*) override {}
- void unregister_filter() override {}
- settings s;
- kalman *filter;
-public slots:
- void doOK();
- void doCancel();
- void updateLabels(const slider_value&);
-};
diff --git a/filter-kalman/kalman_simulation.py b/filter-kalman/kalman_simulation.py
deleted file mode 100644
index 3e792212..00000000
--- a/filter-kalman/kalman_simulation.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# -*- coding: utf-8 -*-
-import numpy as np
-import numpy.matlib as mt
-import matplotlib.pyplot as plt
-
-M = 2
-N = 500
-dt = 1.0 / 30.
-sigma_measure = 0.1
-# x = 1/2 a t**2
-# assume x = 0.5 in 0.5, i.e. head turns half maximal movement range in 0.5 sec.
-# so a = 2 x / t**2 = 4.0
-sigma_accel = 4.0
-
-x = np.zeros(N, dtype = np.float)
-x[N/3:] = 1.0 # step input
-measurement = x.copy()
-measurement += np.random.normal(0., sigma_measure, N)
-
-A = np.matrix([
- [ 1. , dt ],
- [ 0. , 1. ]
-])
-
-R = np.matrix([[ sigma_measure**2 ]])
-
-dv = sigma_accel * dt
-dp = sigma_accel * 0.5 * dt * dt
-Q = np.matrix([
- [ dp*dp, dp*dv ],
- [ dv*dp, dv*dv ]
-])
-
-H = np.matrix([
- [ 1., 0. ],
-])
-
-I = mt.identity(M, dtype = np.float)
-
-def arrayOfMatrices(n, shape):
- return np.asarray([mt.zeros(shape, dtype = np.float) for i in xrange(n)])
-
-# Base on the scipy-cookbook http://scipy-cookbook.readthedocs.io/items/KalmanFiltering.html
-sz_state = (M, 1)
-sz_cov = (M, M)
-sz_K = (M, 1)
-xhat= arrayOfMatrices(N, sz_state) # a posteri estimate of x
-P=arrayOfMatrices(N, sz_cov) # a posteri error estimate
-xhatminus=arrayOfMatrices(N, sz_state) # a priori estimate of x
-Pminus=arrayOfMatrices(N, sz_cov) # a priori error estimate
-K=arrayOfMatrices(N, sz_K) # gain or blending factor
-
-P[0] = mt.ones((M,M)) * 100
-xhat[0] = measurement[0]
-
-for k in range(1,N):
- # time update
- xhatminus[k] = A * xhat[k-1]
- Pminus[k] = A * P[k-1] * A.T + Q
-
- # measurement update
- K[k] = Pminus[k] * H.T * np.linalg.inv( H * Pminus[k] * H.T + R )
- xhat[k] = xhatminus[k] + K[k] * (measurement[k] - H * xhatminus[k])
- P[k] = ( I - K[k]*H ) * Pminus[k]
-
-t = np.arange(N) * dt
-plt.figure()
-plt.subplot(2,1,1)
-plt.plot(t, measurement,'k+',label='noisy measurements')
-plt.plot(t, xhat[:,0,0],'b-',label='position estimate')
-plt.plot(t, x, 'r-', label='ground truth')
-
-plt.subplot(2,1,2)
-plt.plot(t, xhat[:,1,0],'g-',label='velocity estimate')
-
-plt.show()
diff --git a/filter-kalman/lang/nl_NL.ts b/filter-kalman/lang/nl_NL.ts
deleted file mode 100644
index 9e4490a6..00000000
--- a/filter-kalman/lang/nl_NL.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1" language="nl_NL">
-<context>
- <name>KalmanUICdialog_kalman</name>
- <message>
- <source>Kalman settings</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Measurement noise</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Rotation</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Position</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>°</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>-</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-</TS>
diff --git a/filter-kalman/lang/ru_RU.ts b/filter-kalman/lang/ru_RU.ts
deleted file mode 100644
index 33a71087..00000000
--- a/filter-kalman/lang/ru_RU.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1" language="ru_RU">
-<context>
- <name>KalmanUICdialog_kalman</name>
- <message>
- <source>Kalman settings</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Measurement noise</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Rotation</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Position</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>°</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>-</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-</TS>
diff --git a/filter-kalman/lang/stub.ts b/filter-kalman/lang/stub.ts
deleted file mode 100644
index a572be5f..00000000
--- a/filter-kalman/lang/stub.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1">
-<context>
- <name>KalmanUICdialog_kalman</name>
- <message>
- <source>Kalman settings</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Measurement noise</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Rotation</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Position</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>°</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>-</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-</TS>
diff --git a/filter-nm/CMakeLists.txt b/filter-nm/CMakeLists.txt
new file mode 100644
index 00000000..49daaee2
--- /dev/null
+++ b/filter-nm/CMakeLists.txt
@@ -0,0 +1 @@
+otr_module(filter-nm)
diff --git a/filter-nm/ftnoir_filter_nm.cpp b/filter-nm/ftnoir_filter_nm.cpp
new file mode 100644
index 00000000..b222184b
--- /dev/null
+++ b/filter-nm/ftnoir_filter_nm.cpp
@@ -0,0 +1,67 @@
+/* Copyright (c) 2023 Tom Brazier <tom_github@firstsolo.net>
+ *
+ * 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 "ftnoir_filter_nm.h"
+#include "compat/math-imports.hpp"
+#include "compat/macros.h"
+
+#include "api/plugin-api.hpp"
+#include "opentrack/defs.hpp"
+
+#include <algorithm>
+
+filter_nm::filter_nm()
+{
+}
+
+void filter_nm::filter(const double* input, double* output)
+{
+ tVector position = { input[TX], input[TY], input[TZ] };
+ tQuat rotation = QuatFromYPR(input + Yaw);
+
+ // order of axes: x, y, z, yaw, pitch, roll
+ if (unlikely(first_run))
+ {
+ first_run = false;
+ t.start();
+
+ last_pos_speed = tVector();
+ last_rot_speed = tQuat();
+ last_pos_out = position;
+ last_rot_out = rotation;
+ }
+ else
+ {
+ const double dt = t.elapsed_seconds();
+ t.start();
+
+ const tVector pos_speed = (position - last_pos_in) / dt;
+ const double pos_tau = 1. / *s.pos_responsiveness;
+ double alpha = dt / (dt + pos_tau);
+ last_pos_speed += (pos_speed - last_pos_speed) * alpha;
+ const double factor_pos = min(1.0, VectorLength(last_pos_speed) / (*s.pos_drift_speed * 3.0));
+ alpha *= factor_pos * factor_pos;
+ last_pos_out += (position - last_pos_out) * alpha;
+
+ const tQuat rot_delta = QuatDivide(rotation, last_rot_in);
+ constexpr double ms_per_s = 1000.0; // angular speed quaternions need to be small to work so use °/ms
+ const tQuat rot_speed = Slerp(tQuat(), rot_delta, 1.0 / ms_per_s / dt );
+ const double rot_tau = 1. / *s.rot_responsiveness;
+ alpha = dt / (dt + rot_tau);
+ last_rot_speed = Slerp(last_rot_speed, rot_speed, alpha);
+ const double angular_speed = AngleBetween(tQuat(), last_rot_speed) * ms_per_s;
+ const double factor_rot = min(1.0, angular_speed / (*s.rot_drift_speed * 3.0));
+ alpha *= factor_rot * factor_rot;
+ last_rot_out = Slerp(last_rot_out, rotation, alpha);
+ }
+
+ last_pos_in = position;
+ last_rot_in = rotation;
+ std::copy(last_pos_out.v, last_pos_out.v + 3, output + TX);
+ QuatToYPR(last_rot_out, &output[Yaw]);
+}
+
+OPENTRACK_DECLARE_FILTER(filter_nm, dialog_nm, nmDll)
diff --git a/filter-nm/ftnoir_filter_nm.h b/filter-nm/ftnoir_filter_nm.h
new file mode 100644
index 00000000..84775207
--- /dev/null
+++ b/filter-nm/ftnoir_filter_nm.h
@@ -0,0 +1,81 @@
+/* Copyright (c) 2023 Tom Brazier <tom_github@firstsolo.net>
+ *
+ * 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 "ui_ftnoir_nm_filtercontrols.h"
+
+#include "api/plugin-api.hpp"
+#include "compat/timer.hpp"
+#include "compat/hamilton-tools.h"
+#include "options/options.hpp"
+
+using namespace options;
+
+struct settings_nm : opts
+{
+ value<slider_value> pos_responsiveness;
+ value<slider_value> rot_responsiveness;
+ value<slider_value> pos_drift_speed;
+ value<slider_value> rot_drift_speed;
+
+ settings_nm() :
+ opts("nm-filter"),
+ pos_responsiveness(value<slider_value>(b, "pos-responsiveness", { 13.0, .0, 20.0 })),
+ rot_responsiveness(value<slider_value>(b, "rot-responsiveness", { 16.0, .0, 20.0 })),
+ pos_drift_speed(value<slider_value>(b, "pos-drift-speed", { 5.0, 0.1, 50.0 })),
+ rot_drift_speed(value<slider_value>(b, "rot-drift-speed", { 7.0, 0.1, 50.0 }))
+ {
+ }
+};
+
+struct filter_nm : IFilter
+{
+ filter_nm();
+ void filter(const double* input, double* output) override;
+ void center() override { first_run = true; }
+ module_status initialize() override { return status_ok(); }
+
+private:
+ tVector last_pos_in;
+ tQuat last_rot_in;
+ tVector last_pos_out;
+ tQuat last_rot_out;
+ tVector last_pos_speed;
+ tQuat last_rot_speed;
+ Timer t;
+ settings_nm s;
+ bool first_run = true;
+};
+
+class dialog_nm : public IFilterDialog
+{
+ Q_OBJECT
+public:
+ dialog_nm();
+ void register_filter(IFilter*) override {}
+ void unregister_filter() override {}
+ void save() override;
+ void reload() override;
+ bool embeddable() noexcept override { return true; }
+ void set_buttons_visible(bool x) override;
+
+private:
+ Ui::UICdialog_nm ui;
+ settings_nm s;
+
+private slots:
+ void doOK();
+ void doCancel();
+};
+
+class nmDll : public Metadata
+{
+ Q_OBJECT
+
+ QString name() override { return tr("NaturalMovement"); }
+ QIcon icon() override { return QIcon(":/images/filter-16.png"); }
+};
diff --git a/filter-nm/ftnoir_filter_nm_dialog.cpp b/filter-nm/ftnoir_filter_nm_dialog.cpp
new file mode 100644
index 00000000..f3626cae
--- /dev/null
+++ b/filter-nm/ftnoir_filter_nm_dialog.cpp
@@ -0,0 +1,56 @@
+/* Copyright (c) 2023 Tom Brazier <tom_github@firstsolo.net>
+ *
+ * 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 "ftnoir_filter_nm.h"
+
+using namespace options;
+
+dialog_nm::dialog_nm()
+{
+ ui.setupUi(this);
+
+ tie_setting(s.pos_responsiveness, ui.pos_responsiveness_slider);
+ tie_setting(s.pos_responsiveness, ui.pos_responsiveness, [](double x)
+ { return QStringLiteral("%1").arg(x, 0, 'f', 2); });
+
+ tie_setting(s.rot_responsiveness, ui.rot_responsiveness_slider);
+ tie_setting(s.rot_responsiveness, ui.rot_responsiveness, [](double x)
+ { return QStringLiteral("%1").arg(x, 0, 'f', 2); });
+
+ tie_setting(s.pos_drift_speed, ui.pos_drift_speed_slider);
+ tie_setting(s.pos_drift_speed, ui.pos_drift_speed, [](double x)
+ { return QStringLiteral("%1").arg(x, 0, 'f', 2); });
+
+ tie_setting(s.rot_drift_speed, ui.rot_drift_speed_slider);
+ tie_setting(s.rot_drift_speed, ui.rot_drift_speed, [](double x)
+ { return QStringLiteral("%1").arg(x, 0, 'f', 2); });
+}
+
+void dialog_nm::doOK()
+{
+ save();
+ close();
+}
+
+void dialog_nm::doCancel()
+{
+ close();
+}
+
+void dialog_nm::save()
+{
+ s.b->save();
+}
+
+void dialog_nm::reload()
+{
+ s.b->reload();
+}
+
+void dialog_nm::set_buttons_visible(bool x)
+{
+ ui.buttonBox->setVisible(x);
+}
diff --git a/filter-nm/ftnoir_nm_filtercontrols.ui b/filter-nm/ftnoir_nm_filtercontrols.ui
new file mode 100644
index 00000000..7e317e74
--- /dev/null
+++ b/filter-nm/ftnoir_nm_filtercontrols.ui
@@ -0,0 +1,312 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UICdialog_nm</class>
+ <widget class="QDialog" name="UICdialog_nm">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>438</width>
+ <height>559</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Responsiveness</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Rotation</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="pos_responsiveness">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>10.0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="rot_responsiveness">
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>10.0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QSlider" name="rot_responsiveness_slider">
+ <property name="maximum">
+ <number>40</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QSlider" name="pos_responsiveness_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>40</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Position</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Natural movement filter by Tom Brazier: Cancels higher frequency noise and the natural tendency for our heads to drift even when we think we are sitting still.</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Drift speeds</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_14">
+ <property name="text">
+ <string>mm/s</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Position</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="pos_drift_speed">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>50</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="3">
+ <widget class="QSlider" name="rot_drift_speed_slider">
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>500</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Rotation</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLabel" name="rot_drift_speed">
+ <property name="minimumSize">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>100</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QLabel" name="label_24">
+ <property name="text">
+ <string>°/s</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QSlider" name="pos_drift_speed_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>500</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_15">
+ <property name="text">
+ <string>Instructions: Set all sliders to minimum. Then for each of rotation and position: First, increase responsiveness until the filter only just cancels jerkiness for faster head movements. Second, increase drift speed until the filter only just cancels drift movement when your head is still.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>pos_responsiveness_slider</tabstop>
+ <tabstop>rot_responsiveness_slider</tabstop>
+ <tabstop>pos_drift_speed_slider</tabstop>
+ <tabstop>rot_drift_speed_slider</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>UICdialog_nm</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>UICdialog_nm</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/filter-nm/lang/de_DE.ts b/filter-nm/lang/de_DE.ts
new file mode 100644
index 00000000..9cfb1a22
--- /dev/null
+++ b/filter-nm/lang/de_DE.ts
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>UICdialog_nm</name>
+ <message>
+ <source>Dialog</source>
+ <translation>Dialog</translation>
+ </message>
+ <message>
+ <source>Responsiveness</source>
+ <translation>Ansprechempfindlichkeit</translation>
+ </message>
+ <message>
+ <source>Rotation</source>
+ <translation>Rotation</translation>
+ </message>
+ <message>
+ <source>10.0</source>
+ <translation>10,0</translation>
+ </message>
+ <message>
+ <source>Position</source>
+ <translation>Position</translation>
+ </message>
+ <message>
+ <source>Natural movement filter by Tom Brazier: Cancels higher frequency noise and the natural tendency for our heads to drift even when we think we are sitting still.</source>
+ <translation>Natural Movement Filter von Tom Brazier: Hebt hohes Frequenzrauschen auf und unterdrückt die natürlich Tendenz unserer Köpfe, zu driften, selbst wenn wir denken, stillzusitzen.</translation>
+ </message>
+ <message>
+ <source>Drift speeds</source>
+ <translation>Drift-Geschwindigkeiten</translation>
+ </message>
+ <message>
+ <source>mm/s</source>
+ <translation>mm/s</translation>
+ </message>
+ <message>
+ <source>50</source>
+ <translation>50</translation>
+ </message>
+ <message>
+ <source>100</source>
+ <translation>100</translation>
+ </message>
+ <message>
+ <source>°/s</source>
+ <translation>°/s</translation>
+ </message>
+ <message>
+ <source>Instructions: Set all sliders to minimum. Then for each of rotation and position: First, increase responsiveness until the filter only just cancels jerkiness for faster head movements. Second, increase drift speed until the filter only just cancels drift movement when your head is still.</source>
+ <translation>Anleitung: Setze alle Regler auf Minimum. Dann führe folgendes für jede Rotation und Position durch: Erhöhe zunächst die Ansprechempfindlichkeit bis der Filter geradeeben das Ruckeln bei schnellen Kopfbewegungen aufhebt. Als nächstes erhöhe die Driftgeschwindigkeit, bis der Filter knapp das Driften des Kopfes aufhebt, während du ihn nicht bewegst.</translation>
+ </message>
+</context>
+<context>
+ <name>nmDll</name>
+ <message>
+ <source>NaturalMovement</source>
+ <translation>NaturalMovement</translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-tobii-eyex/lang/nl_NL.ts b/filter-nm/lang/nl_NL.ts
index f58d6213..540e2d51 100644
--- a/tracker-tobii-eyex/lang/nl_NL.ts
+++ b/filter-nm/lang/nl_NL.ts
@@ -2,54 +2,60 @@
<!DOCTYPE TS>
<TS version="2.1" language="nl_NL">
<context>
- <name>tobii_eyex_dialog_widgets</name>
+ <name>UICdialog_nm</name>
<message>
- <source>Tracker options</source>
+ <source>Dialog</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Tracking settings</source>
+ <source>°/s</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Accumulative mode shifts the view toward a target that may be offscreen then fixes upon it.
-On the other hand, the snap mode allows for a quick glance outside the field of vision.</source>
+ <source>mm/s</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Accumulative mode settings</source>
+ <source>Responsiveness</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Screen edge length</source>
+ <source>10.0</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Max yaw</source>
+ <source>Drift speeds</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Max pitch</source>
+ <source>50</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Position output</source>
+ <source>100</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Enabled</source>
+ <source>Natural movement filter by Tom Brazier: Cancels higher frequency noise and the natural tendency for our heads to drift even when we think we are sitting still.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Snap mode settings</source>
+ <source>Rotation</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Tracking mode</source>
+ <source>Position</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Speed</source>
+ <source>Instructions: Set all sliders to minimum. Then for each of rotation and position: First, increase responsiveness until the filter only just cancels jerkiness for faster head movements. Second, increase drift speed until the filter only just cancels drift movement when your head is still.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>nmDll</name>
+ <message>
+ <source>NaturalMovement</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-tobii-eyex/lang/ru_RU.ts b/filter-nm/lang/ru_RU.ts
index 844cb931..31e5d5cc 100644
--- a/tracker-tobii-eyex/lang/ru_RU.ts
+++ b/filter-nm/lang/ru_RU.ts
@@ -2,54 +2,60 @@
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
<context>
- <name>tobii_eyex_dialog_widgets</name>
+ <name>UICdialog_nm</name>
<message>
- <source>Tracker options</source>
+ <source>Dialog</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Tracking settings</source>
+ <source>°/s</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Accumulative mode shifts the view toward a target that may be offscreen then fixes upon it.
-On the other hand, the snap mode allows for a quick glance outside the field of vision.</source>
+ <source>mm/s</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Accumulative mode settings</source>
+ <source>Responsiveness</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Screen edge length</source>
+ <source>10.0</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Max yaw</source>
+ <source>Drift speeds</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Max pitch</source>
+ <source>50</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Position output</source>
+ <source>100</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Enabled</source>
+ <source>Natural movement filter by Tom Brazier: Cancels higher frequency noise and the natural tendency for our heads to drift even when we think we are sitting still.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Snap mode settings</source>
+ <source>Rotation</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Tracking mode</source>
+ <source>Position</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Speed</source>
+ <source>Instructions: Set all sliders to minimum. Then for each of rotation and position: First, increase responsiveness until the filter only just cancels jerkiness for faster head movements. Second, increase drift speed until the filter only just cancels drift movement when your head is still.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>nmDll</name>
+ <message>
+ <source>NaturalMovement</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-tobii-eyex/lang/stub.ts b/filter-nm/lang/stub.ts
index a8702a56..3b960804 100644
--- a/tracker-tobii-eyex/lang/stub.ts
+++ b/filter-nm/lang/stub.ts
@@ -1,55 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="stub">
<context>
- <name>tobii_eyex_dialog_widgets</name>
+ <name>UICdialog_nm</name>
<message>
- <source>Tracker options</source>
+ <source>Dialog</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Tracking settings</source>
+ <source>°/s</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Accumulative mode shifts the view toward a target that may be offscreen then fixes upon it.
-On the other hand, the snap mode allows for a quick glance outside the field of vision.</source>
+ <source>mm/s</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Accumulative mode settings</source>
+ <source>Responsiveness</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Screen edge length</source>
+ <source>10.0</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Max yaw</source>
+ <source>Drift speeds</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Max pitch</source>
+ <source>50</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Position output</source>
+ <source>100</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Enabled</source>
+ <source>Natural movement filter by Tom Brazier: Cancels higher frequency noise and the natural tendency for our heads to drift even when we think we are sitting still.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Snap mode settings</source>
+ <source>Rotation</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Tracking mode</source>
+ <source>Position</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Speed</source>
+ <source>Instructions: Set all sliders to minimum. Then for each of rotation and position: First, increase responsiveness until the filter only just cancels jerkiness for faster head movements. Second, increase drift speed until the filter only just cancels drift movement when your head is still.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>nmDll</name>
+ <message>
+ <source>NaturalMovement</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/filter-nm/lang/zh_CN.ts b/filter-nm/lang/zh_CN.ts
new file mode 100644
index 00000000..341f4eee
--- /dev/null
+++ b/filter-nm/lang/zh_CN.ts
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>UICdialog_nm</name>
+ <message>
+ <source>Dialog</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>°/s</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>mm/s</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Responsiveness</source>
+ <translation>å应</translation>
+ </message>
+ <message>
+ <source>10.0</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Drift speeds</source>
+ <translation>漂移速度</translation>
+ </message>
+ <message>
+ <source>50</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>100</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Natural movement filter by Tom Brazier: Cancels higher frequency noise and the natural tendency for our heads to drift even when we think we are sitting still.</source>
+ <translation>Natural movement filter by Tom Brazier: 过滤高频噪声与我们头部的自然飘移, å³ä¾¿æˆ‘ä»¬è®¤ä¸ºæˆ‘ä»¬ä¿æŒä¸åЍ.</translation>
+ </message>
+ <message>
+ <source>Rotation</source>
+ <translation>旋转</translation>
+ </message>
+ <message>
+ <source>Position</source>
+ <translation>ä½ç½®</translation>
+ </message>
+ <message>
+ <source>Instructions: Set all sliders to minimum. Then for each of rotation and position: First, increase responsiveness until the filter only just cancels jerkiness for faster head movements. Second, increase drift speed until the filter only just cancels drift movement when your head is still.</source>
+ <translation>å…ˆå°†æ‰€æœ‰æ»‘å—æœ€å°åŒ–. ç„¶åŽ, 对于æ¯ä¸ªæ—‹è½¬å’Œä½ç½®: 首先æé«˜å“åº”èƒ½åŠ›ï¼Œç›´åˆ°è¿‡æ»¤å™¨åªæ˜¯åˆšå¥½æ¶ˆé™¤æŠ–动(以获得更快的头部è¿åЍ); å…¶æ¬¡ï¼Œå¢žåŠ æ¼‚ç§»é€Ÿåº¦ï¼Œç›´åˆ°è¿‡æ»¤å™¨åªæ˜¯åœ¨å¤´éƒ¨é™æ­¢æ—¶åˆšå¥½æ¶ˆé™¤æ¼‚ç§». </translation>
+ </message>
+</context>
+<context>
+ <name>nmDll</name>
+ <message>
+ <source>NaturalMovement</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/freetrackclient/freetrackclient.c b/freetrackclient/freetrackclient.c
index 2049d91e..80169771 100644
--- a/freetrackclient/freetrackclient.c
+++ b/freetrackclient/freetrackclient.c
@@ -18,10 +18,6 @@
* * created by the FreeTrack developers. *
*/
-#ifndef _MSC_VER
-# warning "expect misnamed symbols"
-#endif
-
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wvariadic-macros"
#pragma GCC diagnostic ignored "-Wunused-parameter"
@@ -32,7 +28,7 @@
#include "fttypes.h"
-#if !defined _WIN64
+#if defined _MSC_VER && !defined _WIN64
# define FT_EXPORT(t) t __stdcall
#else
# define FT_EXPORT(t) __declspec(dllexport) t
diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt
index 954fa67a..8c57221a 100644
--- a/gui/CMakeLists.txt
+++ b/gui/CMakeLists.txt
@@ -1,21 +1,25 @@
otr_module(user-interface BIN)
-target_link_libraries(opentrack-user-interface
+target_link_libraries(${self}
opentrack-migration
opentrack-logic
opentrack-spline
opentrack-pose-widget
- opentrack-version
)
+# for process detector
if(APPLE)
- # for process detector
- target_link_libraries(opentrack-user-interface proc)
+ target_link_libraries(${self} proc)
+elseif(LINUX)
+ otr_pkgconfig_(has-libproc2 ${self} libproc2)
+ if(has-libproc2)
+ target_compile_definitions(${self} PRIVATE -DOTR_HAS_LIBPROC2)
+ else()
+ otr_pkgconfig(${self} libprocps)
+ endif()
endif()
-if(LINUX)
- target_link_libraries(opentrack-user-interface dl)
- # for process detector
- target_link_libraries(opentrack-user-interface procps)
+if(NOT APPLE AND NOT WIN32)
+ target_compile_definitions(${self} PRIVATE -DOTR_X11_THREADS)
+ otr_pkgconfig(${self} x11)
endif()
-
diff --git a/gui/correlation-calibrator.ui b/gui/correlation-calibrator.ui
index 0c33d8b4..02fdf1d2 100644
--- a/gui/correlation-calibrator.ui
+++ b/gui/correlation-calibrator.ui
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
- <string>Form</string>
+ <string notr="true">correlation-calibrator</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
@@ -40,7 +40,7 @@ Press &quot;clear calibration&quot; to remove any calibration data pertaining to
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
- <enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
+ <enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="autoScroll">
<bool>true</bool>
diff --git a/gui/images/english.png b/gui/images/english.png
index 187aabf9..87d9f7d7 100644
--- a/gui/images/english.png
+++ b/gui/images/english.png
Binary files differ
diff --git a/gui/images/settings16.png b/gui/images/settings16.png
index 3b31623b..fa2e4a11 100644
--- a/gui/images/settings16.png
+++ b/gui/images/settings16.png
Binary files differ
diff --git a/gui/init.cpp b/gui/init.cpp
index 42c33c2f..b666b4a9 100644
--- a/gui/init.cpp
+++ b/gui/init.cpp
@@ -1,5 +1,3 @@
-#include "init.hpp"
-
/* Copyright (c) 2013-2017 Stanislaw Halik
*
* Permission to use, copy, modify, and/or distribute this software for any
@@ -7,14 +5,12 @@
* copyright notice and this permission notice appear in all copies.
*/
-#if defined(Q_CREATOR_RUN)
-# pragma clang diagnostic ignored "-Wmain"
-#endif
-
+#include "init.hpp"
#include "migration/migration.hpp"
#include "options/options.hpp"
using namespace options;
#include "compat/library-path.hpp"
+#include "compat/arch.hpp"
#include <memory>
#include <cstdlib>
@@ -25,132 +21,240 @@ using namespace options;
#include <QStyleFactory>
#include <QLocale>
#include <QTranslator>
-#include <QApplication>
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QString>
-#include <QSysInfo>
+#include <QOperatingSystemVersion>
+#include <QMutex>
#include <QDebug>
-#if /* denormal control */ \
- /* GNU */ defined __x86_64__ || defined __SSE2__ || \
- /* MSVC */ defined _M_AMD64 || (defined _M_IX86_FP && _M_IX86_FP >= 2)
-# include <xmmintrin.h>
-# include <pmmintrin.h>
-# include <cfloat>
+#include <cfloat>
+#include <cfenv>
-#define OTR_HAS_DENORM_CONTROL
-void set_fp_mask()
+#ifdef __MINGW32__
+extern "C" __declspec(dllimport) unsigned __cdecl _controlfp(unsigned, unsigned);
+#endif
+
+static void set_fp_mask()
{
- _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
+#if defined OTR_ARCH_DENORM_DAZ
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
- _MM_SET_ROUNDING_MODE(_MM_ROUND_NEAREST);
+#elif defined OTR_ARCH_DENORM_FTZ
+ _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
+#endif
+
+#ifdef OTR_ARCH_FPU_MASK
_MM_SET_EXCEPTION_MASK(_MM_MASK_MASK);
-}
#endif
-void set_qt_style()
+#ifdef __APPLE__
+#if defined __i386__ || defined __x86_64__
+ fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV);
+#endif
+#endif
+
+#ifdef _WIN32
+# ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wreserved-id-macro"
+# endif
+# ifndef _DN_FLUSH
+# define _DN_FLUSH 0x01000000
+# endif
+# ifndef _MCW_DN
+# define _MCW_DN 0x03000000
+# endif
+# ifdef __clang__
+# pragma clang diagnostic pop
+# endif
+ _controlfp(_DN_FLUSH, _MCW_DN);
+#endif
+}
+
+#ifdef OTR_X11_THREADS
+#include <X11/Xlib.h>
+static void enable_x11_threads()
{
-#if defined _WIN32
- if (QSysInfo::WindowsVersion == QSysInfo::WV_XP)
- return;
+ (void)XInitThreads();
+}
#endif
+static void set_qt_style()
+{
#if defined _WIN32 || defined __APPLE__
// our layouts on OSX make some control wrongly sized -sh 20160908
{
- const char* const preferred[] { "fusion", "windowsvista", "macintosh" };
+ const char* const preferred[] {
+#ifdef __APPLE__
+ "macintosh", "fusion", "windowsvista", "windows",
+#else
+ "fusion", "windowsvista", "windows", "windowsxp",
+#endif
+ };
for (const char* style_name : preferred)
- {
- QStyle* s = QStyleFactory::create(style_name);
- if (s)
+ if (QStyle* s = QStyleFactory::create(style_name); s != nullptr)
{
QApplication::setStyle(s);
break;
}
- }
}
#endif
}
+#include <string>
+
#ifdef _WIN32
+# include <windows.h>
+# include <malloc.h>
+#else
+# include <alloca.h>
+#endif
-void qdebug_to_console(QtMsgType, const QMessageLogContext& ctx, const QString &msg)
+static void qdebug_to_console(QtMsgType loglevel, const QMessageLogContext& ctx, const QString &msg)
{
- const unsigned short* const str_ = msg.utf16();
- auto str = reinterpret_cast<const wchar_t* const>(str_);
- static_assert(sizeof(*str_) == sizeof(*str), "");
-
- std::fflush(stderr);
- if (ctx.function)
- std::fprintf(stderr, "[%s:%d%s]: %ls\n", ctx.file, ctx.line, ctx.function, str);
- else if (ctx.file)
- std::fprintf(stderr, "[%s:%d]: %ls\n", ctx.file, ctx.line, str);
+ const char* level;
+
+ switch (loglevel)
+ {
+ default:
+ case QtDebugMsg: level = "DEBUG"; break;
+ case QtWarningMsg: level = "WARN"; break;
+ case QtCriticalMsg: level = "CRIT"; break;
+ case QtFatalMsg: level = "FATAL"; break;
+ case QtInfoMsg: level = "INFO"; break;
+ }
+
+#ifdef _WIN32
+ static_assert(sizeof(wchar_t) == sizeof(decltype(*msg.utf16())));
+
+ if (IsDebuggerPresent())
+ {
+ static QMutex lock;
+ QMutexLocker l(&lock);
+
+ const wchar_t* const bytes = (const wchar_t*)msg.utf16();
+
+ OutputDebugStringW(bytes);
+ OutputDebugStringW(L"\n");
+ }
else
- std::fprintf(stderr, "%ls\n", str);
- std::fflush(stderr);
+#endif
+ {
+#if defined _WIN32
+ const wchar_t* const bytes = (const wchar_t*)msg.utf16();
+#else
+ unsigned len = (unsigned)msg.size()+1;
+ wchar_t* const bytes = (wchar_t*)alloca(len * sizeof(wchar_t));
+ bytes[len-1] = 0;
+ (void)msg.toWCharArray(bytes);
+#endif
+ if (ctx.file)
+ std::fprintf(stderr, "%s [%s:%d]: %ls\n", level, ctx.file, ctx.line, bytes);
+ else
+ std::fprintf(stderr, "%s %ls\n", level, bytes);
+ std::fflush(stderr);
+ }
}
-void add_win32_path()
+#ifdef _WIN32
+
+static void apply_dark_windows_theme_if_needed()
{
- // see https://software.intel.com/en-us/articles/limitation-to-the-length-of-the-system-path-variable
- static char env_path[4096] { '\0', };
+ // On Windows apply dark theme if requested by user settings
+ QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", QSettings::NativeFormat);
+ if (settings.value("AppsUseLightTheme") == 0) {
+ qApp->setStyle(QStyleFactory::create("Dark"));
+ QPalette darkPalette;
+ QColor darkColor = QColor(45, 45, 45);
+ QColor disabledColor = QColor(127, 127, 127);
+ darkPalette.setColor(QPalette::Window, darkColor);
+ darkPalette.setColor(QPalette::WindowText, Qt::white);
+ darkPalette.setColor(QPalette::Base, QColor(18, 18, 18));
+ darkPalette.setColor(QPalette::AlternateBase, darkColor);
+ darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
+ darkPalette.setColor(QPalette::ToolTipText, Qt::white);
+ darkPalette.setColor(QPalette::Text, Qt::white);
+ darkPalette.setColor(QPalette::Disabled, QPalette::Text, disabledColor);
+ darkPalette.setColor(QPalette::Button, darkColor);
+ darkPalette.setColor(QPalette::ButtonText, Qt::white);
+ darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, disabledColor);
+ darkPalette.setColor(QPalette::BrightText, Qt::red);
+ darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
+
+ darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
+ darkPalette.setColor(QPalette::HighlightedText, Qt::black);
+ darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, disabledColor);
+
+ qApp->setPalette(darkPalette);
+
+ qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
+ }
+}
+
+static void add_win32_path()
+{
+ // see https://web.archive.org/web/20180924055536/https://software.intel.com/en-us/articles/limitation-to-the-length-of-the-system-path-variable
{
QString lib_path = OPENTRACK_BASE_PATH;
lib_path.replace("/", "\\");
- const QByteArray lib_path_ = QFile::encodeName(lib_path);
-
QString mod_path = OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH;
mod_path.replace("/", "\\");
- const QByteArray mod_path_ = QFile::encodeName(mod_path);
-
- const char* contents[] {
- "PATH=",
- lib_path_.constData(),
- ";",
- mod_path_.constData(),
- ";",
- getenv("PATH"),
- };
- bool ok = true;
+ const QString orig_path = qgetenv("PATH");
- for (const char* ptr : contents)
- {
- if (ptr)
- strcat_s(env_path, sizeof(env_path), ptr);
+ QString env_path; env_path.reserve(4096);
- if (!ptr || ptr[0] == '\0' || env_path[0] == '\0')
- {
- qDebug() << "bad path element"
- << (ptr == nullptr ? "<null>" : ptr);
- ok = false;
- break;
- }
- }
+#if 0
+ qDebug() << "orig" << orig_path;
+ qDebug() << "libpath" << lib_path;
+ qDebug() << "modpath" << mod_path;
+#endif
- if (ok)
+ if (lib_path.isEmpty())
+ qDebug() << "env: empty lib_path!";
+ else
{
- const int error = _putenv(env_path);
-
- if (error)
- qDebug() << "can't _putenv win32 path";
+ if (!QFile(lib_path).exists())
+ qDebug() << "env: lib_path doesn't exist, this shouldn't happen!";
+ env_path += lib_path;
+ env_path += ';';
}
+ if (mod_path.isEmpty())
+ qDebug() << "env: can't add mod_path to env PATH";
else
- qDebug() << "can't set win32 path";
+ {
+ if (!QFile(mod_path).exists())
+ qDebug() << "env: mod_path doesn't exist, did you install it correctly?";
+ env_path += mod_path;
+ env_path += ';';
+ }
+
+ if (orig_path.isEmpty())
+ qDebug() << "env: empty PATH";
+ else
+ env_path += orig_path;
+
+#if 0
+ qDebug() << "data" << env_path.constData();
+#endif
+
+ // better length limit than putenv() and SetEnvironmentVariableA
+ bool ret = SetEnvironmentVariableW(L"PATH", (const wchar_t*)env_path.constData());
+
+ if (!ret)
+ qDebug() << "_putenv() failed with" << (void*)GetLastError();
}
}
-#include <windows.h>
-
-void attach_parent_console()
+static void attach_parent_console()
{
- std::fflush(stdin);
- std::fflush(stderr);
+ if (GetConsoleWindow() != nullptr)
+ return;
- (void)qInstallMessageHandler(qdebug_to_console);
+ fflush(stdin);
+ fflush(stderr);
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
@@ -159,7 +263,7 @@ void attach_parent_console()
_wfreopen(L"CON", L"r", stdin);
freopen("CON", "w", stdout);
freopen("CON", "w", stderr);
- freopen("CON", "w", stderr);
+ freopen("CON", "r", stdin);
// skip prompt in cmd.exe window
fprintf(stderr, "\n");
@@ -169,35 +273,47 @@ void attach_parent_console()
#endif
-int run_window(QApplication& app, std::unique_ptr<QWidget> main_window)
+static int run_window(std::unique_ptr<QWidget> main_window)
{
if (!main_window->isEnabled())
{
- qDebug() << "exit before window created";
+ qDebug() << "opentrack: exit before window created";
return 2;
}
- app.setQuitOnLastWindowClosed(true);
- int status = app.exec();
+ QApplication::setQuitOnLastWindowClosed(true);
+ int status = QApplication::exec();
return status;
}
-int otr_main(int argc, char** argv, std::function<QWidget*()> make_main_window)
+int otr_main(int argc, char** argv, std::function<std::unique_ptr<QWidget>()> const& make_main_window)
{
-#if defined OTR_HAS_DENORM_CONTROL
+#ifdef _WIN32
+ (void)setvbuf(stderr, nullptr, _IONBF, 0);
+#else
+ (void)setvbuf(stderr, nullptr, _IOLBF, 256);
+#endif
+
set_fp_mask();
+
+#ifdef OTR_X11_THREADS
+ enable_x11_threads();
#endif
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
- QCoreApplication::setAttribute(Qt::AA_X11InitThreads, true);
+ QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QApplication app(argc, argv);
#ifdef _WIN32
- add_win32_path();
attach_parent_console();
#endif
+ (void)qInstallMessageHandler(qdebug_to_console);
+#ifdef _WIN32
+ apply_dark_windows_theme_if_needed();
+ add_win32_path();
+#endif
QDir::setCurrent(OPENTRACK_BASE_PATH);
@@ -213,7 +329,9 @@ int otr_main(int argc, char** argv, std::function<QWidget*()> make_main_window)
qDebug() << "locale:" << forced_locale;
}
- const bool no_i18n = group::with_global_settings_object([](QSettings& s) {
+ using namespace options::globals;
+
+ const bool no_i18n = with_global_settings_object([](QSettings& s) {
return s.value("disable-translation", false).toBool();
});
@@ -224,7 +342,7 @@ int otr_main(int argc, char** argv, std::function<QWidget*()> make_main_window)
}
}
- int ret = run_window(app, std::unique_ptr<QWidget>(make_main_window()));
+ int ret = run_window(make_main_window());
#if 0
// msvc crashes in Qt plugin system's dtor
@@ -236,4 +354,3 @@ int otr_main(int argc, char** argv, std::function<QWidget*()> make_main_window)
return ret;
}
-
diff --git a/gui/init.hpp b/gui/init.hpp
index 1142ff24..28ff55c7 100644
--- a/gui/init.hpp
+++ b/gui/init.hpp
@@ -3,11 +3,10 @@
#include "export.hpp"
#include <functional>
+#include <memory>
#include <QWidget>
-int OTR_GUI_EXPORT otr_main(int argc, char** argv, std::function<QWidget*()> make_main_window);
-
-// XXX TODO need split MainWindow into mixins each implementing part of the functionality
+OTR_GUI_EXPORT int otr_main(int argc, char** argv, std::function<std::unique_ptr<QWidget>()> const& make_main_window);
template<typename F>
auto run_application(int argc, char** argv, F&& fun)
diff --git a/gui/keyboard.cpp b/gui/keyboard.cpp
index 4987d9c0..90849bab 100644
--- a/gui/keyboard.cpp
+++ b/gui/keyboard.cpp
@@ -1,32 +1,43 @@
+#undef NDEBUG
+#include <cassert>
+
#include "keyboard.h"
#include <QDebug>
-keyboard_listener::keyboard_listener(QWidget* parent) :
- QDialog(parent)
-#if defined _WIN32
- , token([this](const Key& k) {
- if(k.guid != "")
- {
- int mods = 0;
- if (k.alt) mods |= Qt::AltModifier;
- if (k.shift) mods |= Qt::ShiftModifier;
- if (k.ctrl) mods |= Qt::ControlModifier;
- joystick_button_pressed(k.guid, k.keycode | mods, k.held);
- }
- else
- {
- Qt::KeyboardModifiers m;
- QKeySequence k_;
- if (win_key::to_qt(k, k_, m))
- key_pressed(static_cast<QVariant>(k_).toInt() | m);
- }
- })
-// token initializer ends, real ctor body begins
+#ifdef _WIN32
+
+void keyboard_listener::receive_key(const Key& k)
+{
+ if(!k.guid.isEmpty())
+ {
+ int mods = 0;
+ if (k.alt) mods |= Qt::AltModifier;
+ if (k.shift) mods |= Qt::ShiftModifier;
+ if (k.ctrl) mods |= Qt::ControlModifier;
+
+ emit joystick_button_pressed(k.guid, k.keycode | mods, k.held);
+ }
+ else
+ {
+ Qt::KeyboardModifiers m;
+ QKeySequence k_;
+
+ if (win_key::to_qt(k, k_, m))
+ for (unsigned i = 0; i < (unsigned)k_.count(); i++)
+ emit key_pressed(QKeySequence(int(m) | k_[i]));
+ }
+}
+
#endif
+
+keyboard_listener::keyboard_listener(QWidget* parent) : QDialog(parent)
{
ui.setupUi(this);
setFocusPolicy(Qt::StrongFocus);
+#ifdef _WIN32
+ (void)token;
+#endif
}
#if !defined _WIN32
diff --git a/gui/keyboard.h b/gui/keyboard.h
index c17a7bad..ea35d372 100644
--- a/gui/keyboard.h
+++ b/gui/keyboard.h
@@ -17,20 +17,24 @@
#include "ui_keyboard_listener.h"
#include <QDialog>
-#include <QKeyEvent>
+#include <QtEvents>
class OTR_GUI_EXPORT keyboard_listener : public QDialog
{
Q_OBJECT
- Ui_keyboard_listener ui;
+
#ifdef _WIN32
- KeybindingWorker::Token token;
+ void receive_key(const Key& k);
+
+ KeybindingWorker::Token token{[this](const Key& k) {receive_key(k);}};
+#else
+ void keyPressEvent(QKeyEvent* event) override;
#endif
+
+ Ui_keyboard_listener ui;
+
public:
keyboard_listener(QWidget* parent = nullptr);
-#ifndef _WIN32
- void keyPressEvent(QKeyEvent* event) override;
-#endif
signals:
void key_pressed(QKeySequence k);
void joystick_button_pressed(QString guid, int idx, bool held);
diff --git a/gui/lang/de_DE.ts b/gui/lang/de_DE.ts
new file mode 100644
index 00000000..8d2dbcae
--- /dev/null
+++ b/gui/lang/de_DE.ts
@@ -0,0 +1,428 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>BrowseButton</name>
+ <message>
+ <source>Set executable name</source>
+ <translation>Namen der ausführbaren Datei setzen</translation>
+ </message>
+ <message>
+ <source>Executable (*.exe);;All Files (*)</source>
+ <translation>Ausführbare Dateien (*.exe);;Alle Dateien (*)</translation>
+ </message>
+</context>
+<context>
+ <name>Form</name>
+ <message>
+ <source>Press &quot;calibrate&quot; in given row to calibrate that axis. Follow instructions in the next window.
+Press &quot;clear calibration&quot; to remove any calibration data pertaining to that axis of position change or rotation.</source>
+ <translation>Klicke „Kalibrieren“ in den angegebenen Zeilen, um die jeweilige Achse zu kalibieren. Folge den Anweisungen im nächsten Fenster.
+Klicke „Kalibrierung löschen“, um jegliche Kalibrierungsdaten der zugehörigen Positions- oder Rotationsachse zu löschen.</translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation>Z</translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation>Gieren</translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation>Nicken</translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation>Rollen</translation>
+ </message>
+ <message>
+ <source>Calibrate</source>
+ <translation>Kalibrieren</translation>
+ </message>
+ <message>
+ <source>Clear calibration</source>
+ <translation>Kalibrierung löschen</translation>
+ </message>
+ <message>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+</context>
+<context>
+ <name>keyboard_listener</name>
+ <message>
+ <source>Dialog</source>
+ <translation>Dialog</translation>
+ </message>
+ <message>
+ <source>Press a key or close this window to remove the keybinding.</source>
+ <translation>Drücke eine Taste oder schließe das Fenster, um die Kurztaste zu löschen.</translation>
+ </message>
+</context>
+<context>
+ <name>mapping_dialog</name>
+ <message>
+ <source>Mapping properties</source>
+ <translation>Abbildungsparameter</translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation>Gieren</translation>
+ </message>
+ <message>
+ <source>Max input</source>
+ <translation>Maximale Eingabe</translation>
+ </message>
+ <message>
+ <source>Asymmetric mapping below</source>
+ <translation>Assymmetrische Abbildung unterhalb</translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation>Nicken</translation>
+ </message>
+ <message>
+ <source>Max output</source>
+ <translation>Maximale Ausgabe</translation>
+ </message>
+ <message>
+ <source>180°</source>
+ <translation>180°</translation>
+ </message>
+ <message>
+ <source>90°</source>
+ <translation>90°</translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation>Rollen</translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation>Z</translation>
+ </message>
+ <message>
+ <source>%1°</source>
+ <translation>%1°</translation>
+ </message>
+ <message>
+ <source>%1 cm</source>
+ <translation>%1 cm</translation>
+ </message>
+</context>
+<context>
+ <name>options_dialog</name>
+ <message>
+ <source>Options</source>
+ <translation>Optionen</translation>
+ </message>
+ <message>
+ <source>Shortcuts</source>
+ <translation>Kurztasten</translation>
+ </message>
+ <message>
+ <source>Global shortcuts</source>
+ <translation>Globale Kurztasten</translation>
+ </message>
+ <message>
+ <source>Use current tracker pose as looking perfectly forward.</source>
+ <translation>Benutze aktuelle Tracker-Pose als perfekt nach vorn gerichtet.</translation>
+ </message>
+ <message>
+ <source>Center</source>
+ <translation>Zentrieren</translation>
+ </message>
+ <message>
+ <source>Bind</source>
+ <translation>Belegen</translation>
+ </message>
+ <message>
+ <source>Start tracking</source>
+ <translation>Tracking starten</translation>
+ </message>
+ <message>
+ <source>Stop tracking</source>
+ <translation>Tracking stoppen</translation>
+ </message>
+ <message>
+ <source>Keep looking forward until next zero keypress.</source>
+ <translation>Weiter vorwärts schauen bis zum nächsten Drücken der Nullen-Taste.</translation>
+ </message>
+ <message>
+ <source>Zero</source>
+ <translation>Nullen</translation>
+ </message>
+ <message>
+ <source>Freeze the position returned by the tracker while this mode is active.</source>
+ <translation>Position des Trackers einfrieren, solange dieser Modus aktiv ist.</translation>
+ </message>
+ <message>
+ <source>Toggle</source>
+ <translation>Umschalten</translation>
+ </message>
+ <message>
+ <source>Zero while held</source>
+ <translation>Nullen während gedrückt</translation>
+ </message>
+ <message>
+ <source>Restart tracking</source>
+ <translation>Tracking neu starten</translation>
+ </message>
+ <message>
+ <source>Toggle while held</source>
+ <translation>Umschalten während gedrückt</translation>
+ </message>
+ <message>
+ <source>Toggle tracking</source>
+ <translation>Tracking umschalten</translation>
+ </message>
+ <message>
+ <source>Disable user interface localization</source>
+ <translation>Übersetzungen der Benutzeroberfläche abschalten</translation>
+ </message>
+ <message>
+ <source>Centering method</source>
+ <translation>Zentriermethode</translation>
+ </message>
+ <message>
+ <source>Center at startup</source>
+ <translation>Zentrieren beim Start</translation>
+ </message>
+ <message>
+ <source>Disabled</source>
+ <translation>Abgeschaltet</translation>
+ </message>
+ <message>
+ <source>Point</source>
+ <translation>Punkt</translation>
+ </message>
+ <message>
+ <source>Wireless VR 360</source>
+ <translation>Wireless VR 360</translation>
+ </message>
+ <message>
+ <source>Roll compensated</source>
+ <translation>rollkompensiert</translation>
+ </message>
+ <message>
+ <source>Minimize to tray</source>
+ <translation>In den Systemabschnitt minimieren</translation>
+ </message>
+ <message>
+ <source>Enable tray</source>
+ <translation>Benachrichtigungssymbol aktivieren</translation>
+ </message>
+ <message>
+ <source>Minimize to tray on startup when enabled</source>
+ <translation>Wenn aktiviert, beim Start in den Systemabschnitt minimieren</translation>
+ </message>
+ <message>
+ <source>Output</source>
+ <translation>Ausgabe</translation>
+ </message>
+ <message>
+ <source>Axis assignment</source>
+ <translation>Achsen-Zuweisung</translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation>Nicken</translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation>Z</translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation>Gieren</translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation>Rollen</translation>
+ </message>
+ <message>
+ <source>Relative translation only</source>
+ <translation>Nur relative Übersetzung</translation>
+ </message>
+ <message>
+ <source>Source</source>
+ <translation>Quelle</translation>
+ </message>
+ <message>
+ <source>Pre-invert</source>
+ <translation>Vor-Invertieren</translation>
+ </message>
+ <message>
+ <source>Destination</source>
+ <translation>Ziel</translation>
+ </message>
+ <message>
+ <source>Post-invert</source>
+ <translation>Nach-Invertieren</translation>
+ </message>
+ <message>
+ <source>Custom center pose</source>
+ <translation>Benutzerdefinierte Mittelpunkt-Pose</translation>
+ </message>
+ <message>
+ <source>Alter the centered position sent to games. Useful if the default position is too much downward or upward.</source>
+ <translation>Verändere die zentrierte Position, die an Spiele gesendet wird. Nützlich, falls die Standard-Position zu weit unten oder oben ist.</translation>
+ </message>
+ <message>
+ <source>°</source>
+ <translation>°</translation>
+ </message>
+ <message>
+ <source> cm</source>
+ <translation> cm</translation>
+ </message>
+ <message>
+ <source>CSV Data Logging</source>
+ <translation>CSV-Datenausgabe</translation>
+ </message>
+ <message>
+ <source>Enable - You will be asked for a filename whenever tracking starts</source>
+ <translation>Einschalten - Der Dateiname wird jedesmal abgefragt, wenn das Tracking startet</translation>
+ </message>
+ <message>
+ <source>Relative translation</source>
+ <translation>Relative Übersetzung</translation>
+ </message>
+ <message>
+ <source>With relative mode on, translation is applied after rotation. For example, rotating +180 degrees yaw and moving backwards results in moving forward as a result of that rotation.</source>
+ <translation>Wenn die relative Modus aktiv ist, wird die Übersetzung nach der Rotation angewendet. Zum Beispiel wird dadurch beim Gieren um 180° mit Rückwärtsbewegung in eine Vorwärtsbewegung umgewandelt.</translation>
+ </message>
+ <message>
+ <source>Mode</source>
+ <translation>Modus</translation>
+ </message>
+ <message>
+ <source>Enabled</source>
+ <translation>Eingeschaltet</translation>
+ </message>
+ <message>
+ <source>Enabled when not aiming</source>
+ <translation>Eingeschaltet, während nicht gezielt wird</translation>
+ </message>
+ <message>
+ <source>Disable for Y</source>
+ <translation>Für Y abschalten</translation>
+ </message>
+ <message>
+ <source>Disable for X</source>
+ <translation>Für X abschalten</translation>
+ </message>
+ <message>
+ <source>Disable effect by roll</source>
+ <translation>Effekt durch Rollen abschalten</translation>
+ </message>
+ <message>
+ <source>Disable for Z (for zoom on Z axis)</source>
+ <translation>Für Z abschalten (für Zoom auf der Z-Achse)</translation>
+ </message>
+ <message>
+ <source>Disable effect by pitch</source>
+ <translation>Effekt durch Nicken abschalten</translation>
+ </message>
+ <message>
+ <source>Disable effect by yaw</source>
+ <translation>Effekt durch Gieren abschalten</translation>
+ </message>
+ <message>
+ <source>Neck displacement</source>
+ <translation>Nacken-Versatz</translation>
+ </message>
+ <message>
+ <source>Eyes will be offset from the pivot of rotation, assumed to be the neck. It also works with relative translation disabled.</source>
+ <translation>Die Augen werden gegenüber des Drehpunktes versetzt sein, unter Annahme, sie seien der Nacken. Dies funktioniert auch dann, wenn die relative Übersetzung abgeschaltet ist.</translation>
+ </message>
+ <message>
+ <source>Enable</source>
+ <translation>Einschalten</translation>
+ </message>
+ <message>
+ <source>Forward from center of rotation</source>
+ <translation>Vom Drehpunkt nach vorn verschieben</translation>
+ </message>
+ <message>
+ <source>Game detection</source>
+ <translation>Spiel-Erkennung</translation>
+ </message>
+ <message>
+ <source>Start tracking automatically when a game starts with selected profile, and stop when the game exits.</source>
+ <translation>Tracking beim Spielstart automatisch mit dem ausgewählten Profil starten, und stoppen, wenn das Spiel beendet wird.</translation>
+ </message>
+ <message>
+ <source>Mouse %1</source>
+ <translation>Maus %1</translation>
+ </message>
+ <message>
+ <source>Joy button %1</source>
+ <translation>Joystick-Knopf %1</translation>
+ </message>
+ <message>
+ <source>None</source>
+ <translation>Nichts</translation>
+ </message>
+ <message>
+ <source>Tracker</source>
+ <translation>Tracker</translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation>Filter</translation>
+ </message>
+</context>
+<context>
+ <name>process_detector</name>
+ <message>
+ <source>Game detector</source>
+ <translation>Spiel-Erkennung</translation>
+ </message>
+ <message>
+ <source>Start profiles from game executable names in this list</source>
+ <translation>Profile anhand der ausführbaren Dateien dieser Liste starten</translation>
+ </message>
+ <message>
+ <source>Executable</source>
+ <translation>Ausführbare Datei</translation>
+ </message>
+ <message>
+ <source>Profile</source>
+ <translation>Profil</translation>
+ </message>
+ <message>
+ <source>+</source>
+ <translation>Hinzufügen</translation>
+ </message>
+ <message>
+ <source>-</source>
+ <translation>Entfernen</translation>
+ </message>
+</context>
+</TS>
diff --git a/gui/lang/nl_NL.ts b/gui/lang/nl_NL.ts
index b49ba1a5..1b9d8fe9 100644
--- a/gui/lang/nl_NL.ts
+++ b/gui/lang/nl_NL.ts
@@ -13,6 +13,50 @@
</message>
</context>
<context>
+ <name>Form</name>
+ <message>
+ <source>Press &quot;calibrate&quot; in given row to calibrate that axis. Follow instructions in the next window.
+Press &quot;clear calibration&quot; to remove any calibration data pertaining to that axis of position change or rotation.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished">X</translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished">Y</translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished">Z</translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation type="unfinished">Yaw</translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation type="unfinished">Pitch</translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation type="unfinished">Rollen</translation>
+ </message>
+ <message>
+ <source>Calibrate</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clear calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>keyboard_listener</name>
<message>
<source>Dialog</source>
@@ -77,6 +121,10 @@
<source>%1°</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>%1 cm</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>options_dialog</name>
@@ -93,10 +141,6 @@
<translation>Algehele sneltoetsen</translation>
</message>
<message>
- <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Center&lt;/span&gt; - use current pose as looking perfectly forward.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Toggle&lt;/span&gt; - keep looking at same spot until next toggle keypress.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Zero&lt;/span&gt; - keep looking forward until next zero keypress.&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
- <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Centreren&lt;/span&gt; - gebruik de huidige kijkrichting als ijkpunt voor het centreren.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Wisselen&lt;/span&gt; - kijkrichting blijft op dezelfde plek staan, totdat de ingestelde knop opnieuw wordt ingedrukt.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Zero&lt;/span&gt; - Kijkrichting blijft recht vooruit, totdat er op de ingestelde toets gedrukt wordt.&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
- </message>
- <message>
<source>Bind</source>
<translation>Koppelen</translation>
</message>
@@ -237,27 +281,15 @@
<translation>Effect uitschakelen bij rol</translation>
</message>
<message>
- <source>Output remap</source>
- <translation>Uitvoer aanpassen</translation>
- </message>
- <message>
- <source>Assign input axis to output axis.</source>
- <translation>Ingangs-as toewijzen aan uitgangs-as.</translation>
- </message>
- <message>
<source>X</source>
<translation>X</translation>
</message>
<message>
- <source>Invert</source>
- <translation>Omkeren</translation>
- </message>
- <message>
<source>Y</source>
<translation>Y</translation>
</message>
<message>
- <source>Never translate the application interface</source>
+ <source>Disable user interface localization</source>
<translation>De applicatie nooit vertaald weergeven</translation>
</message>
<message>
@@ -312,6 +344,58 @@
<source>None</source>
<translation type="unfinished">Geen</translation>
</message>
+ <message>
+ <source>Centering method</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Point</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wireless VR 360</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Roll compensated</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Freeze the position returned by the tracker while this mode is active.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mouse %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use current tracker pose as looking perfectly forward.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Keep looking forward until next zero keypress.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Axis assignment</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Pre-invert</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Post-invert</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>process_detector</name>
diff --git a/gui/lang/ru_RU.ts b/gui/lang/ru_RU.ts
index feb4a302..5b3fcc03 100644
--- a/gui/lang/ru_RU.ts
+++ b/gui/lang/ru_RU.ts
@@ -13,6 +13,50 @@
</message>
</context>
<context>
+ <name>Form</name>
+ <message>
+ <source>Press &quot;calibrate&quot; in given row to calibrate that axis. Follow instructions in the next window.
+Press &quot;clear calibration&quot; to remove any calibration data pertaining to that axis of position change or rotation.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished">X</translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished">Y</translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished">Z</translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation type="unfinished">Yaw</translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation type="unfinished">Pitch</translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation type="unfinished">Roll</translation>
+ </message>
+ <message>
+ <source>Calibrate</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clear calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>keyboard_listener</name>
<message>
<source>Dialog</source>
@@ -77,6 +121,10 @@
<source>%1°</source>
<translation></translation>
</message>
+ <message>
+ <source>%1 cm</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>options_dialog</name>
@@ -93,11 +141,6 @@
<translation>Глобальные горÑчие клавиши</translation>
</message>
<message>
- <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Center&lt;/span&gt; - use current pose as looking perfectly forward.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Toggle&lt;/span&gt; - keep looking at same spot until next toggle keypress.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Zero&lt;/span&gt; - keep looking forward until next zero keypress.&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
- <translatorcomment>иÑпользовать текущую позу как позу вперед</translatorcomment>
- <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Центрирование&lt;/span&gt; - иÑпользовать текущую позу в качеÑтве центра.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;ФикÑÐ°Ñ†Ð¸Ñ Ð¾Ð±Ð·Ð¾Ñ€Ð°&lt;/span&gt; - фикÑÐ°Ñ†Ð¸Ñ Ð¾Ð±Ð·Ð¾Ñ€Ð° до Ñледующего Ð½Ð°Ð¶Ð°Ñ‚Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸ÑˆÐ¸.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Центр&lt;/span&gt; - фикÑирует взглÑд в нулевой точке до Ñледующего Ð½Ð°Ð¶Ð°Ñ‚Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸ÑˆÐ¸.&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
- </message>
- <message>
<source>Bind</source>
<translation>Ðазначить</translation>
</message>
@@ -142,10 +185,6 @@
<translation>Центрирование при запуÑке</translation>
</message>
<message>
- <source>Never translate the application interface</source>
- <translation></translation>
- </message>
- <message>
<source>Minimize to tray</source>
<translation>ÐаÑтройка треÑ</translation>
</message>
@@ -228,22 +267,10 @@
<translation>Смещение точки взглÑда отноÑительно оÑи вращениÑ. Работает вмеÑте Ñ Ð¾Ñ‚Ð½Ð¾Ñительным Ñдвигом</translation>
</message>
<message>
- <source>Output remap</source>
- <translation>Переназначение оÑей</translation>
- </message>
- <message>
- <source>Assign input axis to output axis.</source>
- <translation>СопоÑтавление иÑходных и игровых оÑей.</translation>
- </message>
- <message>
<source>X</source>
<translation>X</translation>
</message>
<message>
- <source>Invert</source>
- <translation>Инвертировать</translation>
- </message>
- <message>
<source>Y</source>
<translation>Y</translation>
</message>
@@ -315,6 +342,62 @@
<source>None</source>
<translation>Ðе назначена</translation>
</message>
+ <message>
+ <source>Centering method</source>
+ <translation>Метод центровки</translation>
+ </message>
+ <message>
+ <source>Point</source>
+ <translation>ТочечнаÑ</translation>
+ </message>
+ <message>
+ <source>Wireless VR 360</source>
+ <translation>БеÑÐ¿Ñ€Ð¾Ð²Ð¾Ð´Ð½Ð°Ñ VR 360</translation>
+ </message>
+ <message>
+ <source>Roll compensated</source>
+ <translation>Roll компенÑированнаÑ</translation>
+ </message>
+ <message>
+ <source>Freeze the position returned by the tracker while this mode is active.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Disable user interface localization</source>
+ <translation>ИÑпользовать английÑкий интерфейÑ</translation>
+ </message>
+ <message>
+ <source>Tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mouse %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use current tracker pose as looking perfectly forward.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Keep looking forward until next zero keypress.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Axis assignment</source>
+ <translation>Переназначение оÑей</translation>
+ </message>
+ <message>
+ <source>Pre-invert</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Post-invert</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>process_detector</name>
diff --git a/gui/lang/stub.ts b/gui/lang/stub.ts
index e559bc28..ee96d3be 100644
--- a/gui/lang/stub.ts
+++ b/gui/lang/stub.ts
@@ -13,6 +13,50 @@
</message>
</context>
<context>
+ <name>Form</name>
+ <message>
+ <source>Press &quot;calibrate&quot; in given row to calibrate that axis. Follow instructions in the next window.
+Press &quot;clear calibration&quot; to remove any calibration data pertaining to that axis of position change or rotation.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Calibrate</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clear calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>keyboard_listener</name>
<message>
<source>Dialog</source>
@@ -77,6 +121,10 @@
<source>%1°</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>%1 cm</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>options_dialog</name>
@@ -93,10 +141,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Center&lt;/span&gt; - use current pose as looking perfectly forward.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Toggle&lt;/span&gt; - keep looking at same spot until next toggle keypress.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Zero&lt;/span&gt; - keep looking forward until next zero keypress.&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Bind</source>
<translation type="unfinished"></translation>
</message>
@@ -141,10 +185,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Never translate the application interface</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Minimize to tray</source>
<translation type="unfinished"></translation>
</message>
@@ -245,22 +285,10 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Output remap</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Assign input axis to output axis.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>X</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Invert</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Y</source>
<translation type="unfinished"></translation>
</message>
@@ -312,6 +340,62 @@
<source>None</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Centering method</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Point</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wireless VR 360</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Roll compensated</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Freeze the position returned by the tracker while this mode is active.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Disable user interface localization</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mouse %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use current tracker pose as looking perfectly forward.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Keep looking forward until next zero keypress.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Axis assignment</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Pre-invert</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Post-invert</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>process_detector</name>
diff --git a/gui/lang/zh_CN.ts b/gui/lang/zh_CN.ts
index d910a860..4914f7e1 100644
--- a/gui/lang/zh_CN.ts
+++ b/gui/lang/zh_CN.ts
@@ -5,76 +5,125 @@
<name>BrowseButton</name>
<message>
<source>Set executable name</source>
- <translation type="unfinished"></translation>
+ <translation>é€‰æ‹©å¯æ‰§è¡Œæ–‡ä»¶</translation>
</message>
<message>
<source>Executable (*.exe);;All Files (*)</source>
- <translation type="unfinished"></translation>
+ <translation>坿‰§è¡Œæ–‡ä»¶ (*.exe);;所有文件 (*)</translation>
+ </message>
+</context>
+<context>
+ <name>Form</name>
+ <message>
+ <source>Press &quot;calibrate&quot; in given row to calibrate that axis. Follow instructions in the next window.
+Press &quot;clear calibration&quot; to remove any calibration data pertaining to that axis of position change or rotation.</source>
+ <translation>按 &quot;校准&quot; in given row to calibrate that axis. 按照下一个窗å£çš„æŒ‡ç¤ºç»§ç»­.
+按 &quot;清除校准&quot; to remove any calibration data pertaining to that axis of position change or rotation.</translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation>航å‘</translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation>仰俯</translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation>横滚</translation>
+ </message>
+ <message>
+ <source>Calibrate</source>
+ <translation>校准</translation>
+ </message>
+ <message>
+ <source>Clear calibration</source>
+ <translation>清除校准</translation>
+ </message>
+ <message>
+ <source>1</source>
+ <translation></translation>
</message>
</context>
<context>
<name>keyboard_listener</name>
<message>
<source>Dialog</source>
- <translation type="unfinished"></translation>
+ <translation>æŒ‰é”®ç»‘å®šå¯¹è¯æ¡†</translation>
</message>
<message>
<source>Press a key or close this window to remove the keybinding.</source>
- <translation type="unfinished"></translation>
+ <translation>æŒ‰é”®ç»‘å®šï¼Œæˆ–è€…å…³é—­çª—å£æ¶ˆé™¤ç»‘定。</translation>
</message>
</context>
<context>
<name>mapping_dialog</name>
<message>
<source>Mapping properties</source>
- <translation type="unfinished"></translation>
+ <translation>映射属性</translation>
</message>
<message>
<source>Yaw</source>
- <translation type="unfinished"></translation>
+ <translation>航å‘</translation>
</message>
<message>
<source>Max input</source>
- <translation type="unfinished"></translation>
+ <translation>最大输入</translation>
</message>
<message>
<source>Asymmetric mapping below</source>
- <translation type="unfinished"></translation>
+ <translation>éžå¯¹ç§°æ˜ å°„</translation>
</message>
<message>
<source>Pitch</source>
- <translation type="unfinished"></translation>
+ <translation>仰俯</translation>
</message>
<message>
<source>Max output</source>
- <translation type="unfinished"></translation>
+ <translation>最大输出</translation>
</message>
<message>
<source>180°</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>90°</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>Roll</source>
- <translation type="unfinished"></translation>
+ <translation>横滚</translation>
</message>
<message>
<source>X</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>Y</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>Z</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>%1°</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>%1 cm</source>
<translation type="unfinished"></translation>
</message>
</context>
@@ -82,234 +131,270 @@
<name>options_dialog</name>
<message>
<source>Options</source>
- <translation type="unfinished"></translation>
+ <translation>选项</translation>
</message>
<message>
<source>Shortcuts</source>
- <translation type="unfinished"></translation>
+ <translation>å¿«æ·é”®</translation>
</message>
<message>
<source>Global shortcuts</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Center&lt;/span&gt; - use current pose as looking perfectly forward.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Toggle&lt;/span&gt; - keep looking at same spot until next toggle keypress.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Zero&lt;/span&gt; - keep looking forward until next zero keypress.&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
- <translation type="unfinished"></translation>
+ <translation>全局快æ·é”®</translation>
</message>
<message>
<source>Center</source>
- <translation type="unfinished"></translation>
+ <translation>回中ä½ç½®</translation>
</message>
<message>
<source>Bind</source>
- <translation type="unfinished"></translation>
+ <translation>绑定</translation>
</message>
<message>
<source>Start tracking</source>
- <translation type="unfinished">开始跟踪</translation>
+ <translation>开始跟踪</translation>
</message>
<message>
<source>Stop tracking</source>
- <translation type="unfinished">åœæ­¢è·Ÿè¸ª</translation>
+ <translation>åœæ­¢è·Ÿè¸ª</translation>
</message>
<message>
<source>Zero</source>
- <translation>åˆå§‹ä½ç½®</translation>
+ <translation>å½’é›¶ä½ç½®</translation>
</message>
<message>
<source>Toggle</source>
- <translation type="unfinished"></translation>
+ <translation>盯ç€çœ‹</translation>
</message>
<message>
<source>Zero while held</source>
- <translation type="unfinished"></translation>
+ <translation>长按归零</translation>
</message>
<message>
<source>Restart tracking</source>
- <translation type="unfinished"></translation>
+ <translation>釿–°å¼€å§‹è·Ÿè¸ª</translation>
</message>
<message>
<source>Toggle while held</source>
- <translation type="unfinished"></translation>
+ <translation>长按盯ç€çœ‹</translation>
</message>
<message>
<source>Toggle tracking</source>
- <translation type="unfinished"></translation>
+ <translation>开关跟踪</translation>
</message>
<message>
<source>Center at startup</source>
- <translation type="unfinished"></translation>
+ <translation>å¯åŠ¨æ—¶å›žä¸­</translation>
</message>
<message>
- <source>Never translate the application interface</source>
- <translation type="unfinished"></translation>
+ <source>Disable user interface localization</source>
+ <translation>关闭翻译界é¢</translation>
</message>
<message>
<source>Minimize to tray</source>
- <translation type="unfinished"></translation>
+ <translation>最å°åŒ–到任务æ </translation>
</message>
<message>
<source>Enable tray</source>
- <translation type="unfinished"></translation>
+ <translation>å¼€å¯ä»»åŠ¡æ å›¾æ ‡</translation>
</message>
<message>
<source>Minimize to tray on startup when enabled</source>
- <translation type="unfinished"></translation>
+ <translation>å¯åŠ¨æ—¶æœ€å°åŒ–到任务æ </translation>
</message>
<message>
<source>Output</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Output remap</source>
- <translation type="unfinished"></translation>
+ <translation>输出</translation>
</message>
<message>
<source>X</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>Y</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>Z</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>Yaw</source>
- <translation type="unfinished"></translation>
+ <translation>航å‘</translation>
</message>
<message>
<source>Pitch</source>
- <translation type="unfinished"></translation>
+ <translation>仰俯</translation>
</message>
<message>
<source>Roll</source>
- <translation type="unfinished"></translation>
+ <translation>横滚</translation>
</message>
<message>
<source>Disabled</source>
- <translation type="unfinished"></translation>
+ <translation>ä¸å¯ç”¨</translation>
</message>
<message>
<source>Relative translation only</source>
- <translation type="unfinished"></translation>
+ <translation>相对ä½ç§»</translation>
</message>
<message>
<source>Source</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Invert</source>
- <translation type="unfinished"></translation>
+ <translation>æºå¤´</translation>
</message>
<message>
<source>Destination</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Assign input axis to output axis.</source>
- <translation type="unfinished"></translation>
+ <translation>目标</translation>
</message>
<message>
<source>Custom center pose</source>
- <translation type="unfinished"></translation>
+ <translation>定制回中姿æ€</translation>
</message>
<message>
<source>Alter the centered position sent to games. Useful if the default position is too much downward or upward.</source>
- <translation type="unfinished"></translation>
+ <translation>改å˜å‘é€åˆ°æ¸¸æˆçš„回中ä½ç½®. 对于默认ä½ç½®æœ‰æ˜Žæ˜¾å移的很有用.</translation>
</message>
<message>
<source>°</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source> cm</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>CSV Data Logging</source>
- <translation type="unfinished"></translation>
+ <translation>CSVæ•°æ®è®°å½•</translation>
</message>
<message>
<source>Enable - You will be asked for a filename whenever tracking starts</source>
- <translation type="unfinished"></translation>
+ <translation>å¯ç”¨ - å½“è·Ÿè¸ªå¼€å§‹æ—¶ä¼šè¢«è¦æ±‚输入文件å</translation>
</message>
<message>
<source>Relative translation</source>
- <translation type="unfinished"></translation>
+ <translation>相对ä½ç§»</translation>
</message>
<message>
<source>With relative mode on, translation is applied after rotation. For example, rotating +180 degrees yaw and moving backwards results in moving forward as a result of that rotation.</source>
- <translation type="unfinished"></translation>
+ <translation>相对ä½ç§»å¼€å¯æ—¶ï¼Œä½ç§»ä¼šè¢«åŽè€ƒè™‘.例如,èˆªå‘æ—‹è½¬+180度,ç„¶åŽåŽé€€çš„结果是,直接å‘å‰ç§»åЍ.</translation>
</message>
<message>
<source>Mode</source>
- <translation type="unfinished"></translation>
+ <translation>模å¼</translation>
</message>
<message>
<source>Enabled</source>
- <translation type="unfinished"></translation>
+ <translation>å¯ç”¨</translation>
</message>
<message>
<source>Enabled when not aiming</source>
- <translation type="unfinished"></translation>
+ <translation>没有æœå‘它时å¯ç”¨</translation>
</message>
<message>
<source>Disable for Y</source>
- <translation type="unfinished"></translation>
+ <translation>Yè½´ä¸å¯ç”¨</translation>
</message>
<message>
<source>Disable for X</source>
- <translation type="unfinished"></translation>
+ <translation>Xè½´ä¸å¯ç”¨</translation>
</message>
<message>
<source>Disable effect by roll</source>
- <translation type="unfinished"></translation>
+ <translation>横滚时,ä¸å¯ç”¨</translation>
</message>
<message>
<source>Disable for Z (for zoom on Z axis)</source>
- <translation type="unfinished"></translation>
+ <translation>Zè½´ä¸å¯ç”¨(用于Z轴放大)</translation>
</message>
<message>
<source>Disable effect by pitch</source>
- <translation type="unfinished"></translation>
+ <translation>仰俯ä¸å¯ç”¨</translation>
</message>
<message>
<source>Disable effect by yaw</source>
- <translation type="unfinished"></translation>
+ <translation>航å‘ä¸å¯ç”¨</translation>
</message>
<message>
<source>Neck displacement</source>
- <translation type="unfinished"></translation>
+ <translation>è„–å­çš„åç§»</translation>
</message>
<message>
<source>Eyes will be offset from the pivot of rotation, assumed to be the neck. It also works with relative translation disabled.</source>
- <translation type="unfinished"></translation>
+ <translation>眼ç›çš„移动支点其实是脖å­ã€‚本功能在相对ä½ç§»åŠŸèƒ½ä¸å¼€å¯çš„æƒ…况下也生效。</translation>
</message>
<message>
<source>Enable</source>
- <translation type="unfinished"></translation>
+ <translation>å¯ç”¨</translation>
</message>
<message>
<source>Forward from center of rotation</source>
- <translation type="unfinished"></translation>
+ <translation>å‘å‰åç§»</translation>
</message>
<message>
<source>Game detection</source>
- <translation type="unfinished"></translation>
+ <translation>游æˆä¾¦æµ‹</translation>
</message>
<message>
<source>Start tracking automatically when a game starts with selected profile, and stop when the game exits.</source>
- <translation type="unfinished"></translation>
+ <translation>当游æˆå¼€å§‹è¿è¡Œæ—¶è‡ªåŠ¨å¼€å§‹è·Ÿè¸ªï¼Œæ¸¸æˆé€€å‡ºæ—¶åœæ­¢è·Ÿè¸ªã€‚</translation>
</message>
<message>
<source>Joy button %1</source>
- <translation type="unfinished"></translation>
+ <translation>æ¸¸æˆæ‰‹æŸ„按钮 %1</translation>
</message>
<message>
<source>None</source>
+ <translation>空</translation>
+ </message>
+ <message>
+ <source>Centering method</source>
+ <translation>回中方法</translation>
+ </message>
+ <message>
+ <source>Point</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wireless VR 360</source>
+ <translation type="unfinished">无线 VR 360</translation>
+ </message>
+ <message>
+ <source>Roll compensated</source>
+ <translation>滚转补å¿</translation>
+ </message>
+ <message>
+ <source>Freeze the position returned by the tracker while this mode is active.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker</source>
+ <translation>输入</translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation>过滤器</translation>
+ </message>
+ <message>
+ <source>Mouse %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use current tracker pose as looking perfectly forward.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Keep looking forward until next zero keypress.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Axis assignment</source>
+ <translation>è¾“å‡ºé‡æ–°æ˜ å°„</translation>
+ </message>
+ <message>
+ <source>Pre-invert</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Post-invert</source>
<translation type="unfinished"></translation>
</message>
</context>
@@ -317,27 +402,27 @@
<name>process_detector</name>
<message>
<source>Game detector</source>
- <translation type="unfinished"></translation>
+ <translation>游æˆä¾¦æµ‹</translation>
</message>
<message>
<source>Start profiles from game executable names in this list</source>
- <translation type="unfinished"></translation>
+ <translation>è¿è¡Œä»¥ä¸‹åˆ—è¡¨å¯æ‰§è¡Œæ–‡ä»¶æ—¶å¯ç”¨å¯¹åº”çš„é…置文件</translation>
</message>
<message>
<source>Executable</source>
- <translation type="unfinished"></translation>
+ <translation>坿‰§è¡Œæ–‡ä»¶</translation>
</message>
<message>
<source>Profile</source>
- <translation type="unfinished"></translation>
+ <translation>é…ç½®</translation>
</message>
<message>
<source>+</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>-</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
</context>
</TS>
diff --git a/gui/mapping-dialog.cpp b/gui/mapping-dialog.cpp
index d4f8a1d4..cdbf532e 100644
--- a/gui/mapping-dialog.cpp
+++ b/gui/mapping-dialog.cpp
@@ -10,6 +10,10 @@
#include "logic/main-settings.hpp"
#include "spline/spline-widget.hpp"
+#include <QtEvents>
+
+using namespace options;
+
mapping_dialog::mapping_dialog(Mappings& m) : m(m), widgets{}
{
ui.setupUi(this);
@@ -24,21 +28,13 @@ mapping_dialog::mapping_dialog(Mappings& m) : m(m), widgets{}
};
{
- QColor bg = palette().background().color();
+ QColor bg = palette().window().color();
QString tmp;
tmp.reserve(32);
- tmp += QStringLiteral(".QWidget { background-color: #");
-
- for (int i : { bg.red(), bg.green(), bg.blue() })
- {
- if (i < 0xf0)
- tmp += '0';
- tmp += QString::number(i, 16);
- }
-
- tmp += "; }";
+ tmp += QStringLiteral(".QWidget { background-color: rgb(%1, %2, %3); } ")
+ .arg(bg.red()).arg(bg.green()).arg(bg.blue());
for (QWidget* w : pages)
w->setStyleSheet(tmp);
@@ -63,6 +59,10 @@ mapping_dialog::mapping_dialog(Mappings& m) : m(m), widgets{}
tie_setting(s.a_y.clamp_x_, ui.max_y_translation);
tie_setting(s.a_z.clamp_x_, ui.max_z_translation);
+ tie_setting(s.a_x.clamp_y_, ui.max_x_out);
+ tie_setting(s.a_y.clamp_y_, ui.max_y_out);
+ tie_setting(s.a_z.clamp_y_, ui.max_z_out);
+
tie_setting(s.a_pitch.clamp_y_, ui.max_pitch_output);
}
@@ -90,20 +90,22 @@ void mapping_dialog::load()
{ nullptr, Yaw, nullptr, false }
};
- ui.max_pitch_output->setItemData(0, int(axis_opts::o_r180));
- ui.max_pitch_output->setItemData(1, int(axis_opts::o_r90));
-
using a = axis_opts::max_clamp;
+ ui.max_pitch_output->setItemData(0, int(a::o_r180));
+ ui.max_pitch_output->setItemData(1, int(a::o_r90));
+
for (QComboBox* x : { ui.max_yaw_rotation, ui.max_pitch_rotation, ui.max_roll_rotation })
for (a y : { a::r180, a::r90, a::r60, a::r45, a::r30, a::r25, a::r20, a::r15, a::r10 })
x->addItem(tr("%1°").arg(y), y);
for (QComboBox* x : { ui.max_x_translation, ui.max_y_translation, ui.max_z_translation })
- for (a y : { a::t30, a::t20, a::t15, a::t10, a::t100 })
- x->addItem(QStringLiteral("%1 cm").arg(int(y)), y);
+ for (a y : { a::t30, a::t20, a::t15, a::t10, a::t75, a::t100, a::t150, a::t300, a::t600 })
+ x->addItem(tr("%1 cm").arg(int(y)), y);
- // XXX TODO add tie_setting overload for spline_widget!!! -sh 20171020
+ for (QComboBox* x : { ui.max_x_out, ui.max_y_out, ui.max_z_out })
+ for (a y : { a::o_t75, a::o_t100, a::o_t150, a::o_t300, a::o_t600 })
+ x->addItem(tr("%1 cm").arg(abs(int(y))), y);
for (int i = 0; qfcs[i].qfc; i++)
{
@@ -116,55 +118,57 @@ void mapping_dialog::load()
if (altp)
{
connect(&axis.opts.altp,
- base_value::value_changed<bool>(),
- this,
- [&](bool f) {qfc.setEnabled(f); qfc.force_redraw();});
+ value_::value_changed<bool>(),
+ this, [&](bool f) { qfc.setEnabled(f); });
qfc.setEnabled(axis.opts.altp);
- qfc.force_redraw();
}
- const int idx = qfcs[i].axis;
-
- using c = axis_opts::max_clamp;
-
- auto update_xstep = [idx, &conf, &qfc](int clamp_x) {
+ auto update_xstep = [&qfc](int clamp_x) {
int value;
- if (clamp_x <= c::r20)
+ if (clamp_x <= a::r30)
value = 1;
- else if (clamp_x <= c::r30)
+ else if (clamp_x <= a::r45)
value = 5;
- else
+ else if (clamp_x <= a::t150)
value = 10;
+ else if (clamp_x <= a::t300)
+ value = 25;
+ else
+ value = 50;
qfc.set_x_step(value);
};
- auto update_ystep = [idx, &conf, &qfc](int clamp_y) {
+ auto update_ystep = [&qfc](int clamp_y) {
int value;
switch (clamp_y)
{
default:
- case c::o_r180:
- value = 15; break;
- case c::o_r90:
+ case a::o_r180:
+ value = 20; break;
+ case a::o_r90:
value = 10; break;
- case c::o_t75:
+ case a::o_t75:
value = 5; break;
+ case a::o_t100:
+ case a::o_t150:
+ value = 10; break;
+ case a::o_t300:
+ value = 50; break;
+ case a::o_t600:
+ value = 100; break;
}
qfc.set_y_step(value);
};
- if (idx >= Yaw)
- qfc.set_snap(.5, 1);
- else
- qfc.set_snap(.5, 1);
+ qfc.set_snap(.5, 1);
- connect(&axis.opts.clamp_x_, base_value::value_changed<int>(), &qfc, update_xstep);
- connect(&axis.opts.clamp_y_, base_value::value_changed<int>(), &qfc, update_ystep);
+ connect(&axis.opts.clamp_x_, value_::value_changed<int>(), &qfc, update_xstep);
+ connect(&axis.opts.clamp_y_, value_::value_changed<int>(), &qfc, update_ystep);
// force signal to avoid duplicating the slot's logic
- qfc.setConfig(&conf);
+ qfc.set_config(&conf);
update_xstep(axis.opts.clamp_x_);
update_ystep(axis.opts.clamp_y_);
diff --git a/gui/mapping-dialog.hpp b/gui/mapping-dialog.hpp
index 23edb696..3758e7c7 100644
--- a/gui/mapping-dialog.hpp
+++ b/gui/mapping-dialog.hpp
@@ -8,9 +8,7 @@
#include <QWidget>
#include <QDialog>
-#include <QShowEvent>
-#include <QCloseEvent>
-#include <QCheckBox>
+#include <QtEvents>
class OTR_GUI_EXPORT mapping_dialog final : public QDialog
{
@@ -18,6 +16,7 @@ class OTR_GUI_EXPORT mapping_dialog final : public QDialog
public:
mapping_dialog(Mappings& m);
void refresh_tab();
+ inline bool embeddable() noexcept { return false; }
private:
Ui::mapping_dialog ui;
Mappings& m;
diff --git a/gui/mapping-dialog.ui b/gui/mapping-dialog.ui
index 0f0aa7c0..545b5ae5 100644
--- a/gui/mapping-dialog.ui
+++ b/gui/mapping-dialog.ui
@@ -46,6 +46,12 @@
<string>Yaw</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>12</number>
+ </property>
+ <property name="rightMargin">
+ <number>12</number>
+ </property>
<item>
<widget class="QFrame" name="frame">
<property name="sizePolicy">
@@ -120,6 +126,12 @@
<string>Pitch</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>12</number>
+ </property>
+ <property name="rightMargin">
+ <number>12</number>
+ </property>
<item>
<widget class="QFrame" name="frame_2">
<property name="sizePolicy">
@@ -215,6 +227,12 @@
<string>Roll</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="leftMargin">
+ <number>12</number>
+ </property>
+ <property name="rightMargin">
+ <number>12</number>
+ </property>
<item>
<widget class="QFrame" name="frame_3">
<property name="sizePolicy">
@@ -293,6 +311,12 @@
<string>X</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="leftMargin">
+ <number>12</number>
+ </property>
+ <property name="rightMargin">
+ <number>12</number>
+ </property>
<item>
<widget class="QFrame" name="frame444">
<property name="sizePolicy">
@@ -328,6 +352,29 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QLabel" name="label4out">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Max output</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="max_x_out">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -367,6 +414,12 @@
<string>Y</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="leftMargin">
+ <number>12</number>
+ </property>
+ <property name="rightMargin">
+ <number>12</number>
+ </property>
<item>
<widget class="QFrame" name="frame555">
<property name="sizePolicy">
@@ -402,6 +455,29 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QLabel" name="label5out">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Max output</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="max_y_out">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -441,6 +517,12 @@
<string>Z</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_7">
+ <property name="leftMargin">
+ <number>12</number>
+ </property>
+ <property name="rightMargin">
+ <number>12</number>
+ </property>
<item>
<widget class="QFrame" name="frame666">
<property name="sizePolicy">
@@ -476,6 +558,29 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QLabel" name="label6out">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Max output</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="max_z_out">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
diff --git a/gui/settings.cpp b/gui/options-dialog.cpp
index fc7cde19..76c3313a 100644
--- a/gui/settings.cpp
+++ b/gui/options-dialog.cpp
@@ -6,19 +6,23 @@
* notice appear in all copies.
*/
-#include "settings.hpp"
+#include "options-dialog.hpp"
#include "keyboard.h"
#include "compat/library-path.hpp"
+
+#include <utility>
+
#include <QPushButton>
#include <QLayout>
#include <QDialog>
#include <QFileDialog>
using namespace options;
+using namespace options::globals;
QString options_dialog::kopts_to_string(const key_opts& kopts)
{
- if (static_cast<QString>(kopts.guid) != "")
+ if (!kopts.guid->isEmpty())
{
const int btn = kopts.button & ~Qt::KeyboardModifierMask;
const int mods = kopts.button & Qt::KeyboardModifierMask;
@@ -26,42 +30,59 @@ QString options_dialog::kopts_to_string(const key_opts& kopts)
if (mods & Qt::ControlModifier) mm += "Control+";
if (mods & Qt::AltModifier) mm += "Alt+";
if (mods & Qt::ShiftModifier) mm += "Shift+";
- return mm + tr("Joy button %1").arg(QString::number(btn));
+ const auto& str = kopts.guid == QStringLiteral("mouse")
+ ? tr("Mouse %1")
+ : tr("Joy button %1");
+ return mm + str.arg(QString::number(btn));
}
- if (static_cast<QString>(kopts.keycode) == "")
+ if (kopts.keycode->isEmpty())
return tr("None");
return kopts.keycode;
}
void options_dialog::set_disable_translation_state(bool value)
{
- group::with_global_settings_object([&](QSettings& s)
+ with_global_settings_object([&](QSettings& s)
{
s.setValue("disable-translation", value);
+ mark_global_ini_modified();
});
}
-options_dialog::options_dialog(std::function<void(bool)> pause_keybindings) :
- pause_keybindings(pause_keybindings)
+options_dialog::options_dialog(std::unique_ptr<ITrackerDialog>& tracker_dialog_,
+ std::unique_ptr<IProtocolDialog>& proto_dialog_,
+ std::unique_ptr<IFilterDialog>& filter_dialog_,
+ std::function<void(bool)> pause_keybindings) :
+ pause_keybindings(std::move(pause_keybindings))
{
ui.setupUi(this);
- connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
- connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
+ connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &options_dialog::doOK);
+ connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &options_dialog::close);
tie_setting(main.tray_enabled, ui.trayp);
tie_setting(main.tray_start, ui.tray_start);
tie_setting(main.center_at_startup, ui.center_at_startup);
+ const centering_state centering_modes[] = {
+ center_disabled,
+ center_point,
+ center_vr360,
+ center_roll_compensated,
+ };
+ for (int k = 0; k < 4; k++)
+ ui.cbox_centering->setItemData(k, centering_modes[k]);
+ tie_setting(main.centering_mode, ui.cbox_centering);
+
const reltrans_state reltrans_modes[] = {
reltrans_disabled,
reltrans_enabled,
reltrans_non_center,
};
- for (unsigned k = 0; k < 3; k++)
- ui.reltrans_mode->setItemData(k, int(reltrans_modes[k]));
+ for (int k = 0; k < 3; k++)
+ ui.reltrans_mode->setItemData(k, reltrans_modes[k]);
tie_setting(main.reltrans_mode, ui.reltrans_mode);
@@ -82,12 +103,19 @@ options_dialog::options_dialog(std::function<void(bool)> pause_keybindings) :
tie_setting(main.a_pitch.zero, ui.pos_ry);
tie_setting(main.a_roll.zero, ui.pos_rz);
- tie_setting(main.a_yaw.invert, ui.invert_yaw);
- tie_setting(main.a_pitch.invert, ui.invert_pitch);
- tie_setting(main.a_roll.invert, ui.invert_roll);
- tie_setting(main.a_x.invert, ui.invert_x);
- tie_setting(main.a_y.invert, ui.invert_y);
- tie_setting(main.a_z.invert, ui.invert_z);
+ tie_setting(main.a_yaw.invert_pre, ui.invert_yaw_pre);
+ tie_setting(main.a_pitch.invert_pre, ui.invert_pitch_pre);
+ tie_setting(main.a_roll.invert_pre, ui.invert_roll_pre);
+ tie_setting(main.a_x.invert_pre, ui.invert_x_pre);
+ tie_setting(main.a_y.invert_pre, ui.invert_y_pre);
+ tie_setting(main.a_z.invert_pre, ui.invert_z_pre);
+
+ tie_setting(main.a_yaw.invert_post, ui.invert_yaw_post);
+ tie_setting(main.a_pitch.invert_post, ui.invert_pitch_post);
+ tie_setting(main.a_roll.invert_post, ui.invert_roll_post);
+ tie_setting(main.a_x.invert_post, ui.invert_x_post);
+ tie_setting(main.a_y.invert_post, ui.invert_y_post);
+ tie_setting(main.a_z.invert_post, ui.invert_z_post);
tie_setting(main.a_yaw.src, ui.src_yaw);
tie_setting(main.a_pitch.src, ui.src_pitch);
@@ -102,7 +130,7 @@ options_dialog::options_dialog(std::function<void(bool)> pause_keybindings) :
tie_setting(main.neck_enable, ui.neck_enable);
- const bool is_translation_disabled = group::with_global_settings_object([] (QSettings& s) {
+ const bool is_translation_disabled = with_global_settings_object([] (QSettings& s) {
return s.value("disable-translation", false).toBool();
});
ui.disable_translation->setChecked(is_translation_disabled);
@@ -147,25 +175,36 @@ options_dialog::options_dialog(std::function<void(bool)> pause_keybindings) :
tmp val = val_;
val.label->setText(kopts_to_string(val.opt));
connect(&val.opt.keycode,
- static_cast<void (base_value::*)(const QString&) const>(&base_value::valueChanged),
+ static_cast<void (value_::*)(const QString&) const>(&value_::valueChanged),
val.label,
[=](const QString&) { val.label->setText(kopts_to_string(val.opt)); });
{
- connect(val.button, &QPushButton::clicked, this, [=]() { bind_key(val.opt, val.label); });
+ connect(val.button, &QPushButton::clicked, this, [=, this] { bind_key(val.opt, val.label); });
}
}
-}
-void options_dialog::closeEvent(QCloseEvent *)
-{
- done(result());
+ auto add_module_tab = [this] (auto& place, auto&& dlg, const QString& label) {
+ if (dlg && dlg->embeddable())
+ {
+ using BaseDialog = plugin_api::detail::BaseDialog;
+
+ dlg->set_buttons_visible(false);
+ place = dlg.release();
+ ui.tabWidget->addTab(place, label);
+ QObject::connect(place, &BaseDialog::closing, this, &QDialog::close);
+ }
+ };
+
+ add_module_tab(tracker_dialog, tracker_dialog_, tr("Tracker"));
+ add_module_tab(proto_dialog, proto_dialog_, tr("Output"));
+ add_module_tab(filter_dialog, filter_dialog_, tr("Filter"));
}
void options_dialog::bind_key(key_opts& kopts, QLabel* label)
{
kopts.button = -1;
- kopts.guid = "";
- kopts.keycode = "";
+ kopts.guid = {};
+ kopts.keycode = {};
auto k = new keyboard_listener;
k->setWindowModality(Qt::ApplicationModal);
k->deleteLater();
@@ -176,7 +215,7 @@ void options_dialog::bind_key(key_opts& kopts, QLabel* label)
[&](const QKeySequence& s)
{
kopts.keycode = s.toString(QKeySequence::PortableText);
- kopts.guid = "";
+ kopts.guid = {};
kopts.button = -1;
k->close();
});
@@ -187,12 +226,12 @@ void options_dialog::bind_key(key_opts& kopts, QLabel* label)
if (!held)
{
kopts.guid = guid;
- kopts.keycode = "";
+ kopts.keycode = {};
kopts.button = idx;
k->close();
}
});
- connect(main.b.get(), &options::detail::bundle::reloading, k, &QDialog::close);
+ connect(&*main.b, &options::detail::bundle::reloading, k, &QDialog::close);
pause_keybindings(true);
k->exec();
pause_keybindings(false);
@@ -204,8 +243,8 @@ void options_dialog::bind_key(key_opts& kopts, QLabel* label)
);
if (is_crap)
{
- kopts.keycode = QStringLiteral("");
- kopts.guid = QStringLiteral("");
+ kopts.keycode = {};
+ kopts.guid = {};
kopts.button = -1;
label->setText(tr("None"));
}
@@ -213,40 +252,152 @@ void options_dialog::bind_key(key_opts& kopts, QLabel* label)
label->setText(kopts_to_string(kopts));
}
-void options_dialog::doOK()
+void options_dialog::switch_to_tracker_tab()
+{
+ if (tracker_dialog)
+ ui.tabWidget->setCurrentWidget(tracker_dialog);
+ else
+ eval_once(qDebug() << "options: asked for tracker tab widget with old-style widget dialog!");
+}
+
+void options_dialog::switch_to_proto_tab()
{
- if (isHidden()) // close() can return true twice in a row it seems
- return;
- hide();
- if (!close()) // dialog was closed already
- return;
+ if (proto_dialog)
+ ui.tabWidget->setCurrentWidget(proto_dialog);
+ else
+ eval_once(qDebug() << "options: asked for proto tab widget with old-style widget dialog!");
+}
+void options_dialog::switch_to_filter_tab()
+{
+ if (filter_dialog)
+ ui.tabWidget->setCurrentWidget(filter_dialog);
+ else
+ eval_once(qDebug() << "options: asked for filter tab widget with old-style widget dialog!");
+}
+
+void options_dialog::tracker_module_changed()
+{
+ if (tracker_dialog)
+ {
+ unregister_tracker();
+ reload();
+ delete tracker_dialog;
+ tracker_dialog = nullptr;
+ }
+}
+
+void options_dialog::proto_module_changed()
+{
+ if (proto_dialog)
+ {
+ unregister_protocol();
+ reload();
+ delete proto_dialog;
+ proto_dialog = nullptr;
+ }
+}
+
+void options_dialog::filter_module_changed()
+{
+ if (filter_dialog)
+ {
+ unregister_filter();
+ reload();
+ delete filter_dialog;
+ filter_dialog = nullptr;
+ }
+}
+
+void options_dialog::register_tracker(ITracker* t)
+{
+ if (tracker_dialog)
+ tracker_dialog->register_tracker(t);
+}
+
+void options_dialog::unregister_tracker()
+{
+ if (tracker_dialog)
+ tracker_dialog->unregister_tracker();
+}
+
+void options_dialog::register_protocol(IProtocol* p)
+{
+ if (proto_dialog)
+ proto_dialog->register_protocol(p);
+}
+
+void options_dialog::unregister_protocol()
+{
+ if (proto_dialog)
+ proto_dialog->unregister_protocol();
+}
+
+void options_dialog::register_filter(IFilter* f)
+{
+ if (filter_dialog)
+ filter_dialog->register_filter(f);
+}
+
+void options_dialog::unregister_filter()
+{
+ if (filter_dialog)
+ filter_dialog->unregister_filter();
+}
+
+using module_list = std::initializer_list<plugin_api::detail::BaseDialog*>;
+
+void options_dialog::save()
+{
main.b->save();
ui.game_detector->save();
set_disable_translation_state(ui.disable_translation->isChecked());
- emit closing();
+
+ for (auto* dlg : module_list{ tracker_dialog, proto_dialog, filter_dialog })
+ if (dlg)
+ dlg->save();
}
-void options_dialog::doCancel()
+void options_dialog::reload()
{
- if (isHidden()) // close() can return true twice in a row it seems
- return;
- hide();
- if (!close()) // dialog was closed already
- return;
+ ui.game_detector->revert();
main.b->reload();
- ui.game_detector->revert();
- emit closing();
+ for (auto* dlg : module_list{ tracker_dialog, proto_dialog, filter_dialog })
+ if (dlg)
+ dlg->reload();
+}
+
+void options_dialog::doOK()
+{
+ if (isVisible())
+ {
+ save();
+ close();
+ }
}
-void options_dialog::done(int res)
+void options_dialog::doCancel()
{
if (isVisible())
{
- if (res == QDialog::Accepted)
- doOK();
- else
- doCancel();
+ reload();
+ close();
}
}
+
+void options_dialog::closeEvent(QCloseEvent *)
+{
+ reload();
+ emit closing();
+}
+
+options_dialog::~options_dialog()
+{
+ if (tracker_dialog)
+ tracker_dialog->unregister_tracker();
+ if (proto_dialog)
+ proto_dialog->unregister_protocol();
+ if (filter_dialog)
+ filter_dialog->unregister_filter();
+}
diff --git a/gui/options-dialog.hpp b/gui/options-dialog.hpp
new file mode 100644
index 00000000..2e5586a5
--- /dev/null
+++ b/gui/options-dialog.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "export.hpp"
+
+#include "gui/ui_options-dialog.h"
+#include "logic/shortcuts.h"
+
+#include <functional>
+
+#include <QObject>
+#include <QDialog>
+#include <QWidget>
+
+class OTR_GUI_EXPORT options_dialog final : public QDialog
+{
+ Q_OBJECT
+signals:
+ void closing();
+public:
+ options_dialog(std::unique_ptr<ITrackerDialog>& tracker_dialog_,
+ std::unique_ptr<IProtocolDialog>& proto_dialog_,
+ std::unique_ptr<IFilterDialog>& filter_dialog_,
+ std::function<void(bool)> pause_keybindings);
+ ~options_dialog() override;
+ inline bool embeddable() noexcept { return false; }
+ void switch_to_tracker_tab();
+ void switch_to_proto_tab();
+ void switch_to_filter_tab();
+ void tracker_module_changed();
+ void proto_module_changed();
+ void filter_module_changed();
+ void register_tracker(ITracker* t);
+ void unregister_tracker();
+ void register_protocol(IProtocol* p);
+ void unregister_protocol();
+ void register_filter(IFilter* f);
+ void unregister_filter();
+ void save();
+ void reload();
+private:
+ void closeEvent(QCloseEvent*) override;
+ static QString kopts_to_string(const key_opts& opts);
+
+ main_settings main;
+ std::function<void(bool)> pause_keybindings;
+ Ui::options_dialog ui;
+
+ ITrackerDialog* tracker_dialog = nullptr;
+ IProtocolDialog* proto_dialog = nullptr;
+ IFilterDialog* filter_dialog = nullptr;
+
+private slots:
+ void doOK();
+ void doCancel();
+ void bind_key(key_opts &kopts, QLabel* label);
+ void set_disable_translation_state(bool value);
+};
diff --git a/gui/options-dialog.ui b/gui/options-dialog.ui
new file mode 100644
index 00000000..9e2c0042
--- /dev/null
+++ b/gui/options-dialog.ui
@@ -0,0 +1,2364 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>options_dialog</class>
+ <widget class="QWidget" name="options_dialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>529</width>
+ <height>533</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Options</string>
+ </property>
+ <property name="windowIcon">
+ <iconset>
+ <normaloff>images/opentrack.png</normaloff>images/opentrack.png</iconset>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="topMargin">
+ <number>5</number>
+ </property>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <property name="usesScrollButtons">
+ <bool>false</bool>
+ </property>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>Shortcuts</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item alignment="Qt::AlignTop">
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Global shortcuts</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_8">
+ <property name="leftMargin">
+ <number>16</number>
+ </property>
+ <item row="1" column="1">
+ <widget class="QLabel" name="toggle_text">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="_textLabel2_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="toolTip">
+ <string>Use current tracker pose as looking perfectly forward.</string>
+ </property>
+ <property name="text">
+ <string>Center</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="4">
+ <widget class="QPushButton" name="bind_toggle_tracking_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="3">
+ <widget class="QLabel" name="restart_tracking_text_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="4">
+ <widget class="QPushButton" name="bind_restart_tracking_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="_textLabel2_7">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>Start tracking</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="2">
+ <widget class="QPushButton" name="bind_stop">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="1">
+ <widget class="QLabel" name="toggle_tracking_text">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="center_text">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="_textLabel2_8">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>Stop tracking</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="zero_text">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="_textLabel2_6">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="toolTip">
+ <string>Keep looking forward until next zero keypress.</string>
+ </property>
+ <property name="text">
+ <string>Zero</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="_textLabel2_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="toolTip">
+ <string>Freeze the position returned by the tracker while this mode is active.</string>
+ </property>
+ <property name="text">
+ <string>Toggle</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="_label_28">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>Zero while held</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QPushButton" name="bind_center">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="toggle_held_text">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLabel" name="zero_held_text">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="bind_toggle_held">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QPushButton" name="bind_zero_held">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
+ <widget class="QLabel" name="_textLabel2_10">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>Restart tracking</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="1">
+ <widget class="QLabel" name="restart_tracking_text">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="_label_27">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>Toggle while held</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="2">
+ <widget class="QPushButton" name="bind_restart_tracking">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="2">
+ <widget class="QPushButton" name="bind_toggle_tracking">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QLabel" name="_textLabel2_9">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>Toggle tracking</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="2">
+ <widget class="QPushButton" name="bind_start">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="bind_toggle">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QLabel" name="start_tracking_text">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QLabel" name="stop_tracking_text">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QLabel" name="toggle_text_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QPushButton" name="bind_toggle_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="center_text_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QPushButton" name="bind_center_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="4">
+ <widget class="QPushButton" name="bind_toggle_held_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QPushButton" name="bind_zero">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QLabel" name="toggle_held_text_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="4">
+ <widget class="QPushButton" name="bind_zero_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="3">
+ <widget class="QLabel" name="zero_held_text_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="3">
+ <widget class="QLabel" name="start_tracking_text_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="3">
+ <widget class="QLabel" name="zero_text_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="4">
+ <widget class="QPushButton" name="bind_zero_held_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="3">
+ <widget class="QLabel" name="stop_tracking_text_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="3">
+ <widget class="QLabel" name="toggle_tracking_text_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="4">
+ <widget class="QPushButton" name="bind_stop_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="4">
+ <widget class="QPushButton" name="bind_start_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Bind</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="disable_translation">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Disable user interface localization</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label_18">
+ <property name="text">
+ <string>Centering method</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="center_at_startup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Center at startup</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="label_29">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="opentrack-res.qrc">:/images/english.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QComboBox" name="cbox_centering">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>3</number>
+ </property>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Point</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Wireless VR 360</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Roll compensated</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_11">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Minimize to tray</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="trayp">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Enable tray</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="tray_start">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Minimize to tray on startup when enabled</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_4">
+ <attribute name="title">
+ <string>Output</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Axis assignment</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <property name="horizontalSpacing">
+ <number>12</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>9</number>
+ </property>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>254</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="1">
+ <widget class="QComboBox" name="src_y">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Relative translation only</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label_13">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>254</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Source</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_14">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>254</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Pre-invert</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>254</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QCheckBox" name="invert_yaw_pre">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="2">
+ <widget class="QCheckBox" name="invert_roll_pre">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QComboBox" name="src_yaw">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="6" column="2">
+ <widget class="QCheckBox" name="invert_x_pre">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="3">
+ <widget class="QCheckBox" name="invert_yaw_post">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QComboBox" name="src_pitch">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>254</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_15">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>254</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Destination</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QCheckBox" name="invert_pitch_pre">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QComboBox" name="src_x">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Relative translation only</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QLabel" name="label_11">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>254</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QComboBox" name="src_roll">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>254</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="label_19">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>254</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Post-invert</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
+ <widget class="QLabel" name="label_12">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>254</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="2">
+ <widget class="QCheckBox" name="invert_z_pre">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="1">
+ <widget class="QComboBox" name="src_z">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Relative translation only</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="7" column="2">
+ <widget class="QCheckBox" name="invert_y_pre">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="3">
+ <widget class="QCheckBox" name="invert_pitch_post">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="3">
+ <widget class="QCheckBox" name="invert_roll_post">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="3">
+ <widget class="QCheckBox" name="invert_x_post">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="3">
+ <widget class="QCheckBox" name="invert_y_post">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="3">
+ <widget class="QCheckBox" name="invert_z_post">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>255</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ <zorder>label_15</zorder>
+ <zorder>label_13</zorder>
+ <zorder>label_14</zorder>
+ <zorder>src_yaw</zorder>
+ <zorder>invert_yaw_pre</zorder>
+ <zorder>label_7</zorder>
+ <zorder>src_pitch</zorder>
+ <zorder>label_8</zorder>
+ <zorder>invert_pitch_pre</zorder>
+ <zorder>label_9</zorder>
+ <zorder>src_roll</zorder>
+ <zorder>invert_roll_pre</zorder>
+ <zorder>label_10</zorder>
+ <zorder>src_x</zorder>
+ <zorder>invert_x_pre</zorder>
+ <zorder>label_11</zorder>
+ <zorder>src_y</zorder>
+ <zorder>invert_y_pre</zorder>
+ <zorder>label_12</zorder>
+ <zorder>src_z</zorder>
+ <zorder>invert_z_pre</zorder>
+ <zorder>label_19</zorder>
+ <zorder>invert_yaw_post</zorder>
+ <zorder>invert_pitch_post</zorder>
+ <zorder>invert_roll_post</zorder>
+ <zorder>invert_x_post</zorder>
+ <zorder>invert_y_post</zorder>
+ <zorder>invert_z_post</zorder>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_6">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Custom center pose</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QLabel" name="label_22">
+ <property name="text">
+ <string>Alter the centered position sent to games. Useful if the default position is too much downward or upward.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="margin">
+ <number>2</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_7">
+ <property name="styleSheet">
+ <string notr="true">QGroupBox {
+ border: 0;
+}</string>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="2" column="1">
+ <widget class="QDoubleSpinBox" name="pos_rz">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="suffix">
+ <string>°</string>
+ </property>
+ <property name="decimals">
+ <number>2</number>
+ </property>
+ <property name="minimum">
+ <double>-180.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>180.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QDoubleSpinBox" name="pos_tz">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="suffix">
+ <string> cm</string>
+ </property>
+ <property name="decimals">
+ <number>3</number>
+ </property>
+ <property name="minimum">
+ <double>-500.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>500.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_4">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string>X</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QDoubleSpinBox" name="pos_tx">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="suffix">
+ <string> cm</string>
+ </property>
+ <property name="decimals">
+ <number>3</number>
+ </property>
+ <property name="minimum">
+ <double>-500.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>500.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Y</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QDoubleSpinBox" name="pos_ry">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="suffix">
+ <string>°</string>
+ </property>
+ <property name="decimals">
+ <number>2</number>
+ </property>
+ <property name="minimum">
+ <double>-180.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>180.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Z</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QDoubleSpinBox" name="pos_ty">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="suffix">
+ <string> cm</string>
+ </property>
+ <property name="decimals">
+ <number>3</number>
+ </property>
+ <property name="minimum">
+ <double>-500.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>500.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QDoubleSpinBox" name="pos_rx">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="suffix">
+ <string>°</string>
+ </property>
+ <property name="decimals">
+ <number>2</number>
+ </property>
+ <property name="minimum">
+ <double>-180.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>180.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_10">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>50</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>CSV Data Logging</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <item>
+ <widget class="QCheckBox" name="tracklogging_enabled">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Enable - You will be asked for a filename whenever tracking starts</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Relative translation</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBox_">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="title">
+ <string>Relative translation</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <widget class="QLabel" name="label_16">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>With relative mode on, translation is applied after rotation. For example, rotating +180 degrees yaw and moving backwards results in moving forward as a result of that rotation.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignJustify|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="margin">
+ <number>2</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label_17">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>10</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Mode</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="reltrans_mode">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Enabled</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Enabled when not aiming</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="tcomp_ty_disable">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>3</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>Disable for Y</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="tcomp_tx_disable">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>3</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>Disable for X</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QCheckBox" name="tcomp_src_roll_disable">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>2</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Disable effect by roll</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="tcomp_tz_disable">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>3</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>Disable for Z (for zoom on Z axis)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QCheckBox" name="tcomp_src_pitch_disable">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>2</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Disable effect by pitch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="tcomp_src_yaw_disable">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>2</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Disable effect by yaw</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_12">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Neck displacement</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_13">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_33">
+ <property name="text">
+ <string>Eyes will be offset from the pivot of rotation, assumed to be the neck. It also works with relative translation disabled.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_4">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_9">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="neck_enable">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>Enable</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="neck_z">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="suffix">
+ <string> cm</string>
+ </property>
+ <property name="minimum">
+ <number>-100</number>
+ </property>
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_32">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>15</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Forward from center of rotation</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_5">
+ <attribute name="title">
+ <string>Game detection</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_9">
+ <item>
+ <widget class="QGroupBox" name="groupBox_1">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Game detection</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <item>
+ <widget class="QLabel" name="label_24">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Start tracking automatically when a game starts with selected profile, and stop when the game exits.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="process_detector" name="game_detector" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>process_detector</class>
+ <extends>QWidget</extends>
+ <header>gui/process_detector.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>tabWidget</tabstop>
+ <tabstop>bind_center</tabstop>
+ <tabstop>bind_toggle</tabstop>
+ <tabstop>bind_toggle_held</tabstop>
+ <tabstop>bind_zero</tabstop>
+ <tabstop>bind_zero_held</tabstop>
+ <tabstop>bind_start</tabstop>
+ <tabstop>bind_stop</tabstop>
+ <tabstop>bind_toggle_tracking</tabstop>
+ <tabstop>bind_restart_tracking</tabstop>
+ <tabstop>bind_center_2</tabstop>
+ <tabstop>bind_toggle_2</tabstop>
+ <tabstop>bind_toggle_held_2</tabstop>
+ <tabstop>bind_zero_2</tabstop>
+ <tabstop>bind_zero_held_2</tabstop>
+ <tabstop>bind_start_2</tabstop>
+ <tabstop>bind_stop_2</tabstop>
+ <tabstop>bind_toggle_tracking_2</tabstop>
+ <tabstop>bind_restart_tracking_2</tabstop>
+ <tabstop>center_at_startup</tabstop>
+ <tabstop>cbox_centering</tabstop>
+ <tabstop>disable_translation</tabstop>
+ <tabstop>trayp</tabstop>
+ <tabstop>tray_start</tabstop>
+ <tabstop>src_yaw</tabstop>
+ <tabstop>invert_yaw_pre</tabstop>
+ <tabstop>invert_yaw_post</tabstop>
+ <tabstop>src_pitch</tabstop>
+ <tabstop>invert_pitch_pre</tabstop>
+ <tabstop>invert_pitch_post</tabstop>
+ <tabstop>src_roll</tabstop>
+ <tabstop>invert_roll_pre</tabstop>
+ <tabstop>invert_roll_post</tabstop>
+ <tabstop>src_x</tabstop>
+ <tabstop>invert_x_pre</tabstop>
+ <tabstop>invert_x_post</tabstop>
+ <tabstop>src_y</tabstop>
+ <tabstop>invert_y_pre</tabstop>
+ <tabstop>invert_y_post</tabstop>
+ <tabstop>src_z</tabstop>
+ <tabstop>invert_z_pre</tabstop>
+ <tabstop>invert_z_post</tabstop>
+ <tabstop>pos_rx</tabstop>
+ <tabstop>pos_ry</tabstop>
+ <tabstop>pos_rz</tabstop>
+ <tabstop>pos_tx</tabstop>
+ <tabstop>pos_ty</tabstop>
+ <tabstop>pos_tz</tabstop>
+ <tabstop>tracklogging_enabled</tabstop>
+ <tabstop>reltrans_mode</tabstop>
+ <tabstop>tcomp_tx_disable</tabstop>
+ <tabstop>tcomp_ty_disable</tabstop>
+ <tabstop>tcomp_tz_disable</tabstop>
+ <tabstop>tcomp_src_yaw_disable</tabstop>
+ <tabstop>tcomp_src_pitch_disable</tabstop>
+ <tabstop>tcomp_src_roll_disable</tabstop>
+ <tabstop>neck_enable</tabstop>
+ <tabstop>neck_z</tabstop>
+ </tabstops>
+ <resources>
+ <include location="opentrack-res.qrc"/>
+ </resources>
+ <connections/>
+ <slots>
+ <slot>startEngineClicked()</slot>
+ <slot>stopEngineClicked()</slot>
+ <slot>cameraSettingsClicked()</slot>
+ </slots>
+</ui>
diff --git a/gui/process_detector.cpp b/gui/process_detector.cpp
index b29df7fb..74e8caac 100644
--- a/gui/process_detector.cpp
+++ b/gui/process_detector.cpp
@@ -18,55 +18,70 @@
#include <QHash>
#include <QPushButton>
#include <QSettings>
+#include <QtEvents>
-static constexpr inline auto RECORD_SEPARATOR = QChar(char(0x1e)); // RS ^]
-static constexpr inline auto UNIT_SEPARATOR = QChar(char(0x1f)); // US ^_
+static constexpr auto RECORD_SEPARATOR = QChar(char(0x1e)); // RS ^]
+static constexpr auto UNIT_SEPARATOR = QChar(char(0x1f)); // US ^_
using namespace options;
+using namespace options::globals;
void proc_detector_settings::set_game_list(const QString &game_list)
{
- group::with_global_settings_object([&](QSettings& settings) {
+ with_global_settings_object([&](QSettings& settings) {
settings.setValue("executable-list", game_list);
+ mark_global_ini_modified();
});
}
QString proc_detector_settings::get_game_list()
{
- return group::with_global_settings_object([&](QSettings& settings) {
+ return with_global_settings_object([&](QSettings& settings) {
return settings.value("executable-list").toString();
});
}
bool proc_detector_settings::is_enabled()
{
- return group::with_global_settings_object([&](QSettings& settings) {
+ return with_global_settings_object([&](QSettings& settings) {
return settings.value("executable-detector-enabled", false).toBool();
});
}
void proc_detector_settings::set_is_enabled(bool enabled)
{
- group::with_global_settings_object([&](QSettings& settings) {
+ with_global_settings_object([&](QSettings& settings) {
settings.setValue("executable-detector-enabled", enabled);
+ mark_global_ini_modified();
});
}
+#ifdef __GNUG__
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
QHash<QString, QString> proc_detector_settings::split_process_names()
{
- QHash<QString, QString> ret;
QString str = get_game_list();
- QStringList pairs = str.split(RECORD_SEPARATOR, QString::SkipEmptyParts);
- for (auto pair : pairs)
+ constexpr auto split_flag =
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ Qt::SkipEmptyParts;
+#else
+ QString::SkipEmptyParts;
+#endif
+ QStringList pairs = str.split(RECORD_SEPARATOR, split_flag);
+ QHash<QString, QString> ret;
+ ret.reserve(pairs.size() * 2);
+ for (auto const& pair : pairs)
{
- QList<QString> tmp = pair.split(UNIT_SEPARATOR);
+ QStringList tmp = pair.split(UNIT_SEPARATOR);
if (tmp.count() != 2)
continue;
if (tmp[0].contains(UNIT_SEPARATOR) || tmp[0].contains(RECORD_SEPARATOR))
continue;
if (tmp[1].contains(UNIT_SEPARATOR) || tmp[1].contains(RECORD_SEPARATOR))
continue;
- if (tmp[0] == QLatin1String("") || tmp[1] == QLatin1String(""))
+ if (tmp[0].isEmpty() || tmp[1].isEmpty())
continue;
ret[tmp[0]] = tmp[1];
}
@@ -75,29 +90,27 @@ QHash<QString, QString> proc_detector_settings::split_process_names()
void BrowseButton::browse()
{
- QFileDialog dialog(this);
- dialog.setFileMode(QFileDialog::ExistingFile);
- QString dir_path = QFileInfo(group::ini_pathname()).absolutePath();
- QString filename = dialog.getOpenFileName(
- this,
- tr("Set executable name"),
- dir_path,
- tr("Executable (*.exe);;All Files (*)"));
+ QString dir_path = QFileInfo(ini_pathname()).absolutePath();
+ QString filename = QFileDialog::getOpenFileName(
+ this,
+ tr("Set executable name"),
+ dir_path,
+ tr("Executable (*.exe);;All Files (*)"));
QDir::setCurrent(OPENTRACK_BASE_PATH);
filename = QFileInfo(filename).fileName();
if (!filename.isNull())
twi->setText(filename);
}
-int process_detector::add_row(QString exe_name, QString profile)
+int process_detector::add_row(QString const& exe_name, QString const& profile)
{
int i = ui.tableWidget->rowCount();
ui.tableWidget->insertRow(i);
QComboBox* cb = new QComboBox();
cb->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum);
- cb->addItem("");
- cb->addItems(group::ini_list());
+ cb->addItems(ini_list());
+ cb->setCurrentText(ini_filename());
ui.tableWidget->setCellWidget(i, 1, cb);
QTableWidgetItem* twi = new QTableWidgetItem(exe_name);
@@ -106,7 +119,7 @@ int process_detector::add_row(QString exe_name, QString profile)
{
BrowseButton* b = new BrowseButton(twi);
b->setText("...");
- QObject::connect(b, SIGNAL(clicked()), b, SLOT(browse()));
+ QObject::connect(b, &BrowseButton::clicked, b, &BrowseButton::browse);
ui.tableWidget->setCellWidget(i, 2, b);
}
@@ -115,13 +128,14 @@ int process_detector::add_row(QString exe_name, QString profile)
return i;
}
-void process_detector::add_items()
+void process_detector::load_rows()
{
+ for (int k = ui.tableWidget->size().height() - 1; k >= 0; k--)
+ ui.tableWidget->removeRow(k);
auto names = s.split_process_names();
- ui.tableWidget->clearContents();
auto keys = names.keys();
std::sort(keys.begin(), keys.end());
- for (auto n : keys)
+ for (auto const& n : keys)
add_row(n, names[n]);
}
@@ -131,13 +145,10 @@ process_detector::process_detector(QWidget* parent) : QWidget(parent)
connect(ui.add, SIGNAL(clicked()), this, SLOT(add()));
connect(ui.remove, SIGNAL(clicked()), this, SLOT(remove()));
- add_items();
+ load_rows();
QResizeEvent e(ui.tableWidget->size(), ui.tableWidget->size());
ui.tableWidget->resizeEvent(&e);
-
- proc_detector_settings s;
-
ui.enabled->setChecked(s.is_enabled());
}
@@ -148,7 +159,10 @@ void process_detector::save()
for (int i = 0; i < ui.tableWidget->rowCount(); i++)
{
auto exe = ui.tableWidget->item(i, 0)->text();
- auto profile = reinterpret_cast<QComboBox*>(ui.tableWidget->cellWidget(i, 1))->currentText();
+ auto widget = qobject_cast<QComboBox*>(ui.tableWidget->cellWidget(i, 1));
+ if (!widget)
+ continue;
+ auto profile = widget->currentText();
str += RECORD_SEPARATOR + exe + UNIT_SEPARATOR + profile;
}
@@ -158,6 +172,8 @@ void process_detector::save()
void process_detector::revert()
{
+ load_rows();
+ ui.enabled->setChecked(s.is_enabled());
}
void process_detector::add()
@@ -174,14 +190,14 @@ void process_detector::remove()
bool process_detector_worker::should_stop()
{
- if (last_exe_name == "")
+ if (last_exe_name == QString())
return false;
proc_detector_settings s;
if (!s.is_enabled())
{
- last_exe_name = "";
+ last_exe_name = QString{};
return false;
}
@@ -190,17 +206,17 @@ bool process_detector_worker::should_stop()
if (exe_list.contains(last_exe_name))
return false;
- last_exe_name = "";
+ last_exe_name = QString{};
return true;
}
-bool process_detector_worker::config_to_start(QString& str)
+bool process_detector_worker::profile_to_start(QString& str)
{
proc_detector_settings s;
if (!s.is_enabled())
{
- last_exe_name = "";
+ last_exe_name = QString{};
return false;
}
@@ -209,20 +225,18 @@ bool process_detector_worker::config_to_start(QString& str)
// assuming manual stop by user button click.
// don't automatically start again while the same process is running.
- if (last_exe_name != "" && exe_list.contains(last_exe_name))
+ if (!last_exe_name.isEmpty() && exe_list.contains(last_exe_name))
return false;
// it's gone, we can start automatically again
- last_exe_name = "";
+ last_exe_name = QString();
- for (auto& name : exe_list)
- {
+ for (auto&& name : exe_list)
if (filenames.contains(name))
{
- last_exe_name = name;
str = filenames[name];
- return str != "";
+ last_exe_name = name;
+ return str != QString{};
}
- }
return false;
}
diff --git a/gui/process_detector.h b/gui/process_detector.h
index 393dafb3..b57d859a 100644
--- a/gui/process_detector.h
+++ b/gui/process_detector.h
@@ -10,22 +10,22 @@
#include "export.hpp"
-#include <QObject>
-#include <QWidget>
-#include <QTableWidget>
-#include <QResizeEvent>
-
#include "gui/ui_process_widget.h"
#include "process-detector-fancy-table.hpp"
#include "options/options.hpp"
+#include <QObject>
+#include <QString>
+#include <QWidget>
+#include <QTableWidget>
+
struct OTR_GUI_EXPORT proc_detector_settings
{
- QHash<QString, QString> split_process_names();
- QString get_game_list();
- void set_game_list(const QString& game_list);
- bool is_enabled();
- void set_is_enabled(bool enabled);
+ static QHash<QString, QString> split_process_names();
+ static QString get_game_list();
+ static void set_game_list(const QString& game_list);
+ static bool is_enabled();
+ static void set_is_enabled(bool enabled);
};
class OTR_GUI_EXPORT process_detector final : public QWidget
@@ -35,8 +35,8 @@ class OTR_GUI_EXPORT process_detector final : public QWidget
Ui_process_detector ui;
proc_detector_settings s;
- int add_row(QString exe_name = "...", QString profile = "");
- void add_items();
+ int add_row(const QString& exe_name = "...", const QString& profile = "");
+ void load_rows();
public:
process_detector(QWidget* parent = nullptr);
public slots:
@@ -61,10 +61,10 @@ public slots:
class OTR_GUI_EXPORT process_detector_worker : QObject
{
Q_OBJECT
- proc_detector_settings s;
QString last_exe_name;
+ proc_detector_settings s;
public:
- bool config_to_start(QString& s);
+ [[nodiscard]] bool profile_to_start(QString& str);
bool should_stop();
};
diff --git a/gui/process_widget.ui b/gui/process_widget.ui
index 9e7b4973..005b0d9b 100644
--- a/gui/process_widget.ui
+++ b/gui/process_widget.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>366</width>
- <height>325</height>
+ <height>380</height>
</rect>
</property>
<property name="windowTitle">
@@ -24,7 +24,7 @@
<item>
<widget class="FancyTable" name="tableWidget">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@@ -83,12 +83,15 @@
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
- <number>5</number>
+ <number>4</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
</property>
<item>
<widget class="QPushButton" name="add">
<property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@@ -108,7 +111,7 @@
<item>
<widget class="QPushButton" name="remove">
<property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
diff --git a/gui/settings-dialog.ui b/gui/settings-dialog.ui
deleted file mode 100644
index 6333692a..00000000
--- a/gui/settings-dialog.ui
+++ /dev/null
@@ -1,2202 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>options_dialog</class>
- <widget class="QWidget" name="options_dialog">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>548</width>
- <height>599</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>Options</string>
- </property>
- <property name="windowIcon">
- <iconset>
- <normaloff>images/opentrack.png</normaloff>images/opentrack.png</iconset>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <property name="topMargin">
- <number>5</number>
- </property>
- <item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="currentIndex">
- <number>0</number>
- </property>
- <property name="usesScrollButtons">
- <bool>false</bool>
- </property>
- <widget class="QWidget" name="tab">
- <attribute name="title">
- <string>Shortcuts</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QGroupBox" name="groupBox_8">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Global shortcuts</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_8">
- <item>
- <widget class="QLabel" name="label_23">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Center&lt;/span&gt; - use current pose as looking perfectly forward.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Toggle&lt;/span&gt; - keep looking at same spot until next toggle keypress.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Zero&lt;/span&gt; - keep looking forward until next zero keypress.&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox">
- <property name="styleSheet">
- <string notr="true">QGroupBox { border: 0; }</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_8">
- <item row="1" column="1">
- <widget class="QLabel" name="toggle_text">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="_textLabel2_3">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Center</string>
- </property>
- <property name="wordWrap">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item row="7" column="4">
- <widget class="QPushButton" name="bind_toggle_tracking_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="8" column="3">
- <widget class="QLabel" name="restart_tracking_text_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="8" column="4">
- <widget class="QPushButton" name="bind_restart_tracking_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="5" column="0">
- <widget class="QLabel" name="_textLabel2_7">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Start tracking</string>
- </property>
- <property name="wordWrap">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item row="6" column="2">
- <widget class="QPushButton" name="bind_stop">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="7" column="1">
- <widget class="QLabel" name="toggle_tracking_text">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="center_text">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="6" column="0">
- <widget class="QLabel" name="_textLabel2_8">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Stop tracking</string>
- </property>
- <property name="wordWrap">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QLabel" name="zero_text">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="_textLabel2_6">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Zero</string>
- </property>
- <property name="wordWrap">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="_textLabel2_5">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Toggle</string>
- </property>
- <property name="wordWrap">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item row="4" column="0">
- <widget class="QLabel" name="_label_28">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Zero while held</string>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QPushButton" name="bind_center">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLabel" name="toggle_held_text">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QLabel" name="zero_held_text">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QPushButton" name="bind_toggle_held">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="4" column="2">
- <widget class="QPushButton" name="bind_zero_held">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="8" column="0">
- <widget class="QLabel" name="_textLabel2_10">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Restart tracking</string>
- </property>
- <property name="wordWrap">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item row="8" column="1">
- <widget class="QLabel" name="restart_tracking_text">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="_label_27">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Toggle while held</string>
- </property>
- </widget>
- </item>
- <item row="8" column="2">
- <widget class="QPushButton" name="bind_restart_tracking">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="7" column="2">
- <widget class="QPushButton" name="bind_toggle_tracking">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="7" column="0">
- <widget class="QLabel" name="_textLabel2_9">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Toggle tracking</string>
- </property>
- <property name="wordWrap">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item row="5" column="2">
- <widget class="QPushButton" name="bind_start">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QPushButton" name="bind_toggle">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="5" column="1">
- <widget class="QLabel" name="start_tracking_text">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="6" column="1">
- <widget class="QLabel" name="stop_tracking_text">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="1" column="3">
- <widget class="QLabel" name="toggle_text_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="1" column="4">
- <widget class="QPushButton" name="bind_toggle_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="0" column="3">
- <widget class="QLabel" name="center_text_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="4">
- <widget class="QPushButton" name="bind_center_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="2" column="4">
- <widget class="QPushButton" name="bind_toggle_held_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="3" column="2">
- <widget class="QPushButton" name="bind_zero">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="2" column="3">
- <widget class="QLabel" name="toggle_held_text_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="3" column="4">
- <widget class="QPushButton" name="bind_zero_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="4" column="3">
- <widget class="QLabel" name="zero_held_text_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="5" column="3">
- <widget class="QLabel" name="start_tracking_text_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="3" column="3">
- <widget class="QLabel" name="zero_text_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="4" column="4">
- <widget class="QPushButton" name="bind_zero_held_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="6" column="3">
- <widget class="QLabel" name="stop_tracking_text_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="7" column="3">
- <widget class="QLabel" name="toggle_tracking_text_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="6" column="4">
- <widget class="QPushButton" name="bind_stop_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- <item row="5" column="4">
- <widget class="QPushButton" name="bind_start_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Bind</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QFrame" name="frame_5">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShape">
- <enum>QFrame::StyledPanel</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Sunken</enum>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="0">
- <widget class="QCheckBox" name="center_at_startup">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Center at startup</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QCheckBox" name="disable_translation">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Never translate the application interface</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="label_29">
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="opentrack-res.qrc">:/images/english.png</pixmap>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_11">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Minimize to tray</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_3">
- <property name="bottomMargin">
- <number>9</number>
- </property>
- <item>
- <widget class="QCheckBox" name="trayp">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Enable tray</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="tray_start">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Minimize to tray on startup when enabled</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="tab_4">
- <attribute name="title">
- <string>Output</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_4">
- <property name="topMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QGroupBox" name="groupBox_4">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <kerning>true</kerning>
- </font>
- </property>
- <property name="title">
- <string>Output remap</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignHCenter|Qt::AlignTop</set>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_4">
- <item row="1" column="0">
- <widget class="QGroupBox" name="groupBox_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="styleSheet">
- <string notr="true">QGroupBox
-{
- border: 0;
-}</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_5">
- <property name="horizontalSpacing">
- <number>12</number>
- </property>
- <property name="verticalSpacing">
- <number>9</number>
- </property>
- <item row="7" column="1">
- <widget class="QComboBox" name="src_y">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <item>
- <property name="text">
- <string>X</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Y</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Z</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Yaw</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Pitch</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Roll</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Disabled</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Relative translation only</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="6" column="0">
- <widget class="QLabel" name="label_10">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>254</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>X</string>
- </property>
- </widget>
- </item>
- <item row="5" column="0">
- <widget class="QLabel" name="label_9">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>254</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Roll</string>
- </property>
- </widget>
- </item>
- <item row="7" column="2">
- <widget class="QCheckBox" name="invert_y">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="4" column="2">
- <widget class="QCheckBox" name="invert_pitch">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_7">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>254</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Yaw</string>
- </property>
- </widget>
- </item>
- <item row="6" column="2">
- <widget class="QCheckBox" name="invert_x">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="label_13">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>254</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Source</string>
- </property>
- </widget>
- </item>
- <item row="5" column="1">
- <widget class="QComboBox" name="src_roll">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <item>
- <property name="text">
- <string>X</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Y</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Z</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Yaw</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Pitch</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Roll</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Disabled</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="3" column="2">
- <widget class="QCheckBox" name="invert_yaw">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QComboBox" name="src_pitch">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <item>
- <property name="text">
- <string>X</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Y</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Z</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Yaw</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Pitch</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Roll</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Disabled</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="8" column="0">
- <widget class="QLabel" name="label_12">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>254</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Z</string>
- </property>
- </widget>
- </item>
- <item row="8" column="2">
- <widget class="QCheckBox" name="invert_z">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="8" column="1">
- <widget class="QComboBox" name="src_z">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <item>
- <property name="text">
- <string>X</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Y</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Z</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Yaw</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Pitch</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Roll</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Disabled</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Relative translation only</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QComboBox" name="src_yaw">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <item>
- <property name="text">
- <string>X</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Y</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Z</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Yaw</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Pitch</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Roll</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Disabled</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="5" column="2">
- <widget class="QCheckBox" name="invert_roll">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>255</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QLabel" name="label_14">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>254</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Invert</string>
- </property>
- </widget>
- </item>
- <item row="6" column="1">
- <widget class="QComboBox" name="src_x">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <item>
- <property name="text">
- <string>X</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Y</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Z</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Yaw</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Pitch</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Roll</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Disabled</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Relative translation only</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_15">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>254</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Destination</string>
- </property>
- </widget>
- </item>
- <item row="7" column="0">
- <widget class="QLabel" name="label_11">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>254</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Y</string>
- </property>
- </widget>
- </item>
- <item row="4" column="0">
- <widget class="QLabel" name="label_8">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>254</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Pitch</string>
- </property>
- </widget>
- </item>
- </layout>
- <zorder>label_15</zorder>
- <zorder>label_13</zorder>
- <zorder>label_14</zorder>
- <zorder>src_yaw</zorder>
- <zorder>invert_yaw</zorder>
- <zorder>label_7</zorder>
- <zorder>src_pitch</zorder>
- <zorder>label_8</zorder>
- <zorder>invert_pitch</zorder>
- <zorder>label_9</zorder>
- <zorder>src_roll</zorder>
- <zorder>invert_roll</zorder>
- <zorder>label_10</zorder>
- <zorder>src_x</zorder>
- <zorder>invert_x</zorder>
- <zorder>label_11</zorder>
- <zorder>src_y</zorder>
- <zorder>invert_y</zorder>
- <zorder>label_12</zorder>
- <zorder>src_z</zorder>
- <zorder>invert_z</zorder>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_20">
- <property name="text">
- <string>Assign input axis to output axis.</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignJustify|Qt::AlignVCenter</set>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_6">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Custom center pose</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_6">
- <item>
- <widget class="QLabel" name="label_22">
- <property name="text">
- <string>Alter the centered position sent to games. Useful if the default position is too much downward or upward.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- <property name="margin">
- <number>2</number>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_7">
- <property name="styleSheet">
- <string notr="true">QGroupBox {
- border: 0;
-}</string>
- </property>
- <property name="title">
- <string/>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="2" column="1">
- <widget class="QDoubleSpinBox" name="pos_rz">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="suffix">
- <string>°</string>
- </property>
- <property name="decimals">
- <number>2</number>
- </property>
- <property name="minimum">
- <double>-180.000000000000000</double>
- </property>
- <property name="maximum">
- <double>180.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="2" column="3">
- <widget class="QDoubleSpinBox" name="pos_tz">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="suffix">
- <string> cm</string>
- </property>
- <property name="decimals">
- <number>3</number>
- </property>
- <property name="minimum">
- <double>-500.000000000000000</double>
- </property>
- <property name="maximum">
- <double>500.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>X</string>
- </property>
- </widget>
- </item>
- <item row="0" column="3">
- <widget class="QDoubleSpinBox" name="pos_tx">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="suffix">
- <string> cm</string>
- </property>
- <property name="decimals">
- <number>3</number>
- </property>
- <property name="minimum">
- <double>-500.000000000000000</double>
- </property>
- <property name="maximum">
- <double>500.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Pitch</string>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string>Y</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QDoubleSpinBox" name="pos_ry">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="suffix">
- <string>°</string>
- </property>
- <property name="decimals">
- <number>2</number>
- </property>
- <property name="minimum">
- <double>-180.000000000000000</double>
- </property>
- <property name="maximum">
- <double>180.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QLabel" name="label_6">
- <property name="text">
- <string>Z</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Roll</string>
- </property>
- </widget>
- </item>
- <item row="1" column="3">
- <widget class="QDoubleSpinBox" name="pos_ty">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="suffix">
- <string> cm</string>
- </property>
- <property name="decimals">
- <number>3</number>
- </property>
- <property name="minimum">
- <double>-500.000000000000000</double>
- </property>
- <property name="maximum">
- <double>500.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Yaw</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QDoubleSpinBox" name="pos_rx">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="suffix">
- <string>°</string>
- </property>
- <property name="decimals">
- <number>2</number>
- </property>
- <property name="minimum">
- <double>-180.000000000000000</double>
- </property>
- <property name="maximum">
- <double>180.000000000000000</double>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_10">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>50</height>
- </size>
- </property>
- <property name="title">
- <string>CSV Data Logging</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_11">
- <item>
- <widget class="QCheckBox" name="tracklogging_enabled">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>1</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Enable - You will be asked for a filename whenever tracking starts</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="tab_2">
- <attribute name="title">
- <string>Relative translation</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QGroupBox" name="groupBox_">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="styleSheet">
- <string notr="true"/>
- </property>
- <property name="title">
- <string>Relative translation</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_7">
- <item>
- <widget class="QLabel" name="label_16">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>With relative mode on, translation is applied after rotation. For example, rotating +180 degrees yaw and moving backwards results in moving forward as a result of that rotation.</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignJustify|Qt::AlignVCenter</set>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- <property name="margin">
- <number>2</number>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="widget" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="QLabel" name="label_17">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>10</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Mode</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="reltrans_mode">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>4</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <item>
- <property name="text">
- <string>Disabled</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Enabled</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Enabled when not aiming</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QFrame" name="frame_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <property name="spacing">
- <number>0</number>
- </property>
- <item row="2" column="0">
- <widget class="QCheckBox" name="tcomp_ty_disable">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>3</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="styleSheet">
- <string notr="true"/>
- </property>
- <property name="text">
- <string>Disable for Y</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QCheckBox" name="tcomp_tx_disable">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>3</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="styleSheet">
- <string notr="true"/>
- </property>
- <property name="text">
- <string>Disable for X</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QCheckBox" name="tcomp_src_roll_disable">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>2</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Disable effect by roll</string>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QCheckBox" name="tcomp_tz_disable">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>3</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="styleSheet">
- <string notr="true"/>
- </property>
- <property name="text">
- <string>Disable for Z (for zoom on Z axis)</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QCheckBox" name="tcomp_src_pitch_disable">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>2</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Disable effect by pitch</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QCheckBox" name="tcomp_src_yaw_disable">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>2</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Disable effect by yaw</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_12">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Neck displacement</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_13">
- <property name="spacing">
- <number>7</number>
- </property>
- <item>
- <widget class="QLabel" name="label_33">
- <property name="text">
- <string>Eyes will be offset from the pivot of rotation, assumed to be the neck. It also works with relative translation disabled.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- <property name="margin">
- <number>0</number>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QFrame" name="frame_4">
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <layout class="QGridLayout" name="gridLayout_9">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>9</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <property name="spacing">
- <number>0</number>
- </property>
- <item row="0" column="0">
- <widget class="QCheckBox" name="neck_enable">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="styleSheet">
- <string notr="true"/>
- </property>
- <property name="text">
- <string>Enable</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QSpinBox" name="neck_z">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>4</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="suffix">
- <string> cm</string>
- </property>
- <property name="minimum">
- <number>-100</number>
- </property>
- <property name="maximum">
- <number>100</number>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_32">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>15</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Forward from center of rotation</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="tab_5">
- <attribute name="title">
- <string>Game detection</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_9">
- <item>
- <widget class="QGroupBox" name="groupBox_1">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Game detection</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_12">
- <item>
- <widget class="QLabel" name="label_24">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Start tracking automatically when a game starts with selected profile, and stop when the game exits.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="process_detector" name="game_detector" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </widget>
- </item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <customwidgets>
- <customwidget>
- <class>process_detector</class>
- <extends>QWidget</extends>
- <header>gui/process_detector.h</header>
- </customwidget>
- </customwidgets>
- <tabstops>
- <tabstop>tabWidget</tabstop>
- <tabstop>bind_center</tabstop>
- <tabstop>bind_toggle</tabstop>
- <tabstop>bind_toggle_held</tabstop>
- <tabstop>bind_zero</tabstop>
- <tabstop>bind_zero_held</tabstop>
- <tabstop>bind_start</tabstop>
- <tabstop>bind_stop</tabstop>
- <tabstop>bind_toggle_tracking</tabstop>
- <tabstop>bind_restart_tracking</tabstop>
- <tabstop>bind_center_2</tabstop>
- <tabstop>bind_toggle_2</tabstop>
- <tabstop>bind_toggle_held_2</tabstop>
- <tabstop>bind_zero_2</tabstop>
- <tabstop>bind_zero_held_2</tabstop>
- <tabstop>bind_start_2</tabstop>
- <tabstop>bind_stop_2</tabstop>
- <tabstop>bind_toggle_tracking_2</tabstop>
- <tabstop>bind_restart_tracking_2</tabstop>
- <tabstop>center_at_startup</tabstop>
- <tabstop>disable_translation</tabstop>
- <tabstop>trayp</tabstop>
- <tabstop>tray_start</tabstop>
- <tabstop>src_yaw</tabstop>
- <tabstop>invert_yaw</tabstop>
- <tabstop>src_pitch</tabstop>
- <tabstop>invert_pitch</tabstop>
- <tabstop>src_roll</tabstop>
- <tabstop>invert_roll</tabstop>
- <tabstop>src_x</tabstop>
- <tabstop>invert_x</tabstop>
- <tabstop>src_y</tabstop>
- <tabstop>invert_y</tabstop>
- <tabstop>src_z</tabstop>
- <tabstop>invert_z</tabstop>
- <tabstop>tracklogging_enabled</tabstop>
- <tabstop>tcomp_tx_disable</tabstop>
- <tabstop>tcomp_ty_disable</tabstop>
- <tabstop>tcomp_tz_disable</tabstop>
- <tabstop>tcomp_src_yaw_disable</tabstop>
- <tabstop>tcomp_src_pitch_disable</tabstop>
- <tabstop>tcomp_src_roll_disable</tabstop>
- <tabstop>neck_enable</tabstop>
- <tabstop>neck_z</tabstop>
- </tabstops>
- <resources>
- <include location="opentrack-res.qrc"/>
- </resources>
- <connections/>
- <slots>
- <slot>startEngineClicked()</slot>
- <slot>stopEngineClicked()</slot>
- <slot>cameraSettingsClicked()</slot>
- </slots>
-</ui>
diff --git a/gui/settings.hpp b/gui/settings.hpp
deleted file mode 100644
index 63e109a4..00000000
--- a/gui/settings.hpp
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-
-#include "export.hpp"
-
-#include "gui/ui_settings-dialog.h"
-#include "logic/shortcuts.h"
-#include <QObject>
-#include <QDialog>
-#include <QWidget>
-#include <functional>
-
-class OTR_GUI_EXPORT options_dialog : public QDialog
-{
- Q_OBJECT
-signals:
- void closing();
-public:
- options_dialog(std::function<void(bool)> pause_keybindings);
-private:
- main_settings main;
- std::function<void(bool)> pause_keybindings;
- Ui::options_dialog ui;
- void closeEvent(QCloseEvent *) override;
- static QString kopts_to_string(const key_opts& opts);
-private slots:
- void doOK();
- void doCancel();
- void done(int res) override;
- void bind_key(key_opts &kopts, QLabel* label);
- void set_disable_translation_state(bool value);
-};
diff --git a/installer/.gitignore b/installer/.gitignore
new file mode 100644
index 00000000..f569d96c
--- /dev/null
+++ b/installer/.gitignore
@@ -0,0 +1 @@
+/Output
diff --git a/installer/opentrack-installer.iss b/installer/opentrack-installer.iss
index e6551863..960ddf7f 100644
--- a/installer/opentrack-installer.iss
+++ b/installer/opentrack-installer.iss
@@ -1,15 +1,13 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
-#include "../build/opentrack-version.h"
+#include "../build/opentrack-version.hxx"
#define MyAppName "opentrack"
#define MyAppVersion OPENTRACK_VERSION
#define MyAppPublisher "opentrack"
#define MyAppURL "http://github.com/opentrack/opentrack"
#define MyAppExeName "opentrack.exe"
-#include "non-ui-blocking-exec.iss"
-
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
@@ -22,11 +20,11 @@ AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
-DefaultDirName={pf}\{#MyAppName}
+DefaultDirName={commonpf}\{#MyAppName}
DefaultGroupName={#MyAppName}
AllowNoIcons=yes
OutputBaseFilename={#MyAppVersion}-win32-setup
-SetupIconFile=..\variant\default\opentrack.ico
+SetupIconFile=..\opentrack\opentrack.ico
Compression=lzma2/ultra64
SolidCompression=yes
DisableWelcomePage=True
@@ -36,7 +34,6 @@ RestartIfNeededByRun=False
InternalCompressLevel=ultra
CompressionThreads=4
LZMANumFastBytes=273
-MinVersion=0,5.01sp2
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
@@ -44,6 +41,9 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
+[InstallDelete]
+Type: filesandordirs; Name: "{app}"
+
[Files]
Source: "..\build\install\*"; DestDir: "{app}"; Flags: ignoreversion createallsubdirs recursesubdirs
@@ -52,94 +52,6 @@ Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
-Filename: "timeout.exe"; Parameters: "/t 0"; Flags: runhidden; StatusMsg: Installing RealSense Runtime. This may take several minutes.; Check: RSCameraDriverDetectedAndEulaAccepted
Filename: "{app}\{#MyAppExeName}"; Flags: nowait postinstall skipifsilent; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}";
[Code]
-var
- EulaAccepted:Boolean;
- RSCameraDriverDetected:Boolean;
- EULAPage: TOutputMsgMemoWizardPage;
- EULAAcceptRadioButton: TNewRadioButton;
- EULARefuseRadioButton: TNewRadioButton;
-
-function IsRSCameraDriverPresent():Boolean;
-var
- Version:String;
-begin
- result := RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\Intel\RSSDK\Components\ivcam',
- 'Version', Version) or RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\Wow6432Node\Intel\RSDCM\SR300',
- 'Version', Version)
-end;
-
-procedure EULAAcceptRefuseButtonStateUpdated(Sender: TObject);
-begin
- EulaAccepted := EULAAcceptRadioButton.Checked
-end;
-
-
-function RSCameraDriverDetectedAndEulaAccepted():Boolean;
-begin
-result := RSCameraDriverDetected and EulaAccepted;
-end;
-
-procedure DoPostInstall();
-var
- Version: String;
- ExecInfo: TShellExecuteInfo;
-begin
-if RSCameraDriverDetectedAndEulaAccepted then
- begin
- NonUiBlockingExec(ExpandConstant('{app}\doc\contrib\intel_rs_sdk_runtime_websetup_10.0.26.0396.exe'),
- '--silent --no-progress --acceptlicense=yes --front --finstall=core,face3d --fnone=all');
- end
-end;
-
-procedure CurStepChanged(CurStep: TSetupStep);
- begin
- if CurStep = ssPostInstall then
- begin
- DoPostInstall()
- end
-end;
-
-procedure InitializeWizard();
-var
- EULAText: AnsiString;
-begin
-
-EulaAccepted := false
-RSCameraDriverDetected := IsRSCameraDriverPresent()
-
-if RSCameraDriverDetected then
- begin
- ExtractTemporaryFile('RS_EULA.rtf');
- if LoadStringFromFile(ExpandConstant('{tmp}\RS_EULA.rtf'), EULAText) then
- begin
- EULAPage := CreateOutputMsgMemoPage(wpLicense,
- 'Information', 'Intel RealSense End-User License agreement',
- 'Opentrack may use the Intel RealSense SDK Runtime on your compatible system. To get it installed, please accept its EULA:',
- EULAText);
- EULAPage.RichEditViewer.Height := ScaleY(148);
- EULAAcceptRadioButton := TNewRadioButton.Create(EULAPage);
- EULAAcceptRadioButton.Left := EULAPage.RichEditViewer.Left;
- EULAAcceptRadioButton.Top := EULAPage.Surface.ClientHeight - ScaleY(41);
- EULAAcceptRadioButton.Width := EULAPage.RichEditViewer.Width;
- EULAAcceptRadioButton.Parent := EULAPage.Surface;
- EULAAcceptRadioButton.Caption := SetupMessage(msgLicenseAccepted);
- EULARefuseRadioButton := TNewRadioButton.Create(EULAPage);
- EULARefuseRadioButton.Left := EULAPage.RichEditViewer.Left;
- EULARefuseRadioButton.Top := EULAPage.Surface.ClientHeight - ScaleY(21);
- EULARefuseRadioButton.Width := EULAPage.RichEditViewer.Width;
- EULARefuseRadioButton.Parent := EULAPage.Surface;
- EULARefuseRadioButton.Caption := SetupMessage(msgLicenseNotAccepted);
-
- // Set the states and event handlers
- EULAAcceptRadioButton.OnClick := @EULAAcceptRefuseButtonStateUpdated;
- EULARefuseRadioButton.OnClick := @EULAAcceptRefuseButtonStateUpdated;
- EULARefuseRadioButton.Checked := true;
- EulaAccepted := EULAAcceptRadioButton.Checked
- //TODO: if camera is detected, activate RS EULA page and RSSDK install, save if it was accepted or not.
- end
- end
-end;
diff --git a/logic/CMakeLists.txt b/logic/CMakeLists.txt
index f3f128af..f3344798 100644
--- a/logic/CMakeLists.txt
+++ b/logic/CMakeLists.txt
@@ -1,7 +1,7 @@
otr_module(logic BIN)
-target_link_libraries(opentrack-logic opentrack-spline)
+target_link_libraries(${self} opentrack-spline)
if(NOT WIN32)
- target_link_libraries(opentrack-logic opentrack-qxt-mini)
+ target_link_libraries(${self} opentrack-qxt-mini)
else()
- target_link_libraries(opentrack-logic opentrack-dinput winmm)
+ target_link_libraries(${self} opentrack-dinput winmm)
endif()
diff --git a/logic/extensions.cpp b/logic/extensions.cpp
deleted file mode 100644
index 22eef6df..00000000
--- a/logic/extensions.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-#include "extensions.hpp"
-
-#include <functional>
-
-using namespace options;
-
-using ext_fun_type = void(IExtension::*)(Pose&);
-using ext_mask = IExtension::event_mask;
-using ext_ord = IExtension::event_ordinal;
-
-static constexpr struct event_type_mapping
-{
- ext_fun_type ptr;
- ext_mask mask;
- ext_ord idx;
-} ordinal_to_function[] = {
- { &IExtension::process_raw, ext_mask::on_raw, ext_ord::ev_raw, },
- { &IExtension::process_before_filter, ext_mask::on_before_filter, ext_ord::ev_before_filter, },
- { &IExtension::process_before_mapping, ext_mask::on_before_mapping, ext_ord::ev_before_mapping, },
- { &IExtension::process_finished, ext_mask::on_finished, ext_ord::ev_finished, },
-};
-
-bool event_handler::is_enabled(const QString& name)
-{
- (void)name;
-#if 1
- return true;
-#else
- if (!ext_bundle->contains(name))
- return false;
-
- return ext_bundle->get<bool>(name);
-#endif
-}
-
-event_handler::event_handler(Modules::dylib_list const& extensions) : ext_bundle(make_bundle("extensions"))
-{
- for (std::shared_ptr<dylib> const& lib : extensions)
- {
- std::shared_ptr<IExtension> ext(reinterpret_cast<IExtension*>(lib->Constructor()));
- std::shared_ptr<IExtensionDialog> dlg(reinterpret_cast<IExtensionDialog*>(lib->Dialog()));
- std::shared_ptr<Metadata> m(reinterpret_cast<Metadata*>(lib->Meta()));
-
- const ext_mask mask = ext->hook_types();
-
- if (!is_enabled(lib->module_name))
- continue;
-
-#if 0
- qDebug() << "extension" << lib->module_name << "mask" << (void*)mask;
-#endif
-
- for (event_type_mapping const& mapping : ordinal_to_function)
- {
- const unsigned i = mapping.idx;
- const ext_mask mask_ = mapping.mask;
-
- if (mask & mask_)
- extensions_for_event[i].push_back({ ext, dlg, m });
- }
- }
-}
-
-void event_handler::run_events(event_ordinal k, Pose& pose)
-{
- auto fun = std::mem_fn(ordinal_to_function[k].ptr);
-
- for (extension& x : extensions_for_event[k])
- fun(*x.logic, pose);
-}
-
diff --git a/logic/extensions.hpp b/logic/extensions.hpp
deleted file mode 100644
index 4d6763b2..00000000
--- a/logic/extensions.hpp
+++ /dev/null
@@ -1,44 +0,0 @@
-#pragma once
-
-/* Copyright (c) 2017 Stanislaw Halik
- *
- * 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 "api/plugin-support.hpp"
-#include "options/options.hpp"
-
-#include <vector>
-#include <array>
-
-#include "export.hpp"
-
-struct OTR_LOGIC_EXPORT event_handler final
-{
- using event_ordinal = IExtension::event_ordinal;
-
- struct extension
- {
- using ext = std::shared_ptr<IExtension>;
- using dlg = std::shared_ptr<IExtensionDialog>;
- using m = std::shared_ptr<Metadata>;
-
- ext logic;
- dlg dialog;
- m metadata;
- };
-
- void run_events(event_ordinal k, Pose& pose);
- event_handler(Modules::dylib_list const& extensions);
-
-private:
- using ext_list = std::vector<extension>;
- std::array<ext_list, IExtension::event_count> extensions_for_event;
-
- options::bundle ext_bundle;
-
- bool is_enabled(const QString& name);
-};
-
diff --git a/logic/lang/de_DE.ts b/logic/lang/de_DE.ts
new file mode 100644
index 00000000..eca10616
--- /dev/null
+++ b/logic/lang/de_DE.ts
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>Work</name>
+ <message>
+ <source>Select filename</source>
+ <translation>Dateiname wählen</translation>
+ </message>
+ <message>
+ <source>CSV File (*.csv)</source>
+ <translation>CSV-Datei (*.csv)</translation>
+ </message>
+ <message>
+ <source>Logging error</source>
+ <translation>Protokollierungsfehler</translation>
+ </message>
+ <message>
+ <source>Unable to open file &apos;%1&apos;. Proceeding without logging.</source>
+ <translation>Datei &apos;%1&apos; kann nicht geöffnet werden. Es wird ohne Protokollierung fortgefahren.</translation>
+ </message>
+</context>
+<context>
+ <name>runtime_libraries</name>
+ <message>
+ <source>Library load failure</source>
+ <translation>Fehler beim Laden der Bibliothek</translation>
+ </message>
+ <message>
+ <source>Error occurred while loading protocol %1
+
+%2
+</source>
+ <translation>Ein Fehler trat auf beim Laden des Protokolls %1
+
+%2
+</translation>
+ </message>
+ <message>
+ <source>Error occurred while loading filter %1
+
+%2
+</source>
+ <translation>Fehler beim Laden des Filters %1
+
+%2
+</translation>
+ </message>
+ <message>
+ <source>Error occurred while loading tracker %1
+
+%2
+</source>
+ <translation>Fehler beim Laden des Trackers %1
+
+%2
+</translation>
+ </message>
+ <message>
+ <source>Startup failure</source>
+ <translation>Ausführungsfehler</translation>
+ </message>
+</context>
+</TS>
diff --git a/logic/lang/nl_NL.ts b/logic/lang/nl_NL.ts
index 9e739505..85b50bd8 100644
--- a/logic/lang/nl_NL.ts
+++ b/logic/lang/nl_NL.ts
@@ -1,4 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="nl_NL">
+<context>
+ <name>Work</name>
+ <message>
+ <source>Select filename</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>CSV File (*.csv)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Logging error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unable to open file &apos;%1&apos;. Proceeding without logging.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>runtime_libraries</name>
+ <message>
+ <source>Library load failure</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Error occurred while loading protocol %1
+
+%2
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Error occurred while loading filter %1
+
+%2
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Error occurred while loading tracker %1
+
+%2
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Startup failure</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/logic/lang/ru_RU.ts b/logic/lang/ru_RU.ts
index f62cf2e1..7772438c 100644
--- a/logic/lang/ru_RU.ts
+++ b/logic/lang/ru_RU.ts
@@ -1,4 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
+<context>
+ <name>Work</name>
+ <message>
+ <source>Select filename</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>CSV File (*.csv)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Logging error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unable to open file &apos;%1&apos;. Proceeding without logging.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>runtime_libraries</name>
+ <message>
+ <source>Library load failure</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Error occurred while loading protocol %1
+
+%2
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Error occurred while loading filter %1
+
+%2
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Error occurred while loading tracker %1
+
+%2
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Startup failure</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/logic/lang/stub.ts b/logic/lang/stub.ts
index 6401616d..cc15f98a 100644
--- a/logic/lang/stub.ts
+++ b/logic/lang/stub.ts
@@ -1,4 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
+<context>
+ <name>Work</name>
+ <message>
+ <source>Select filename</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>CSV File (*.csv)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Logging error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unable to open file &apos;%1&apos;. Proceeding without logging.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>runtime_libraries</name>
+ <message>
+ <source>Library load failure</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Error occurred while loading protocol %1
+
+%2
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Error occurred while loading filter %1
+
+%2
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Error occurred while loading tracker %1
+
+%2
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Startup failure</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/logic/lang/zh_CN.ts b/logic/lang/zh_CN.ts
index 6401616d..000aff7c 100644
--- a/logic/lang/zh_CN.ts
+++ b/logic/lang/zh_CN.ts
@@ -1,4 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>Work</name>
+ <message>
+ <source>Select filename</source>
+ <translation>选择文件å</translation>
+ </message>
+ <message>
+ <source>CSV File (*.csv)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Logging error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unable to open file &apos;%1&apos;. Proceeding without logging.</source>
+ <translation type="unfinished">未能打开文件 &apos;%1&apos;. Proceeding without logging.</translation>
+ </message>
+</context>
+<context>
+ <name>runtime_libraries</name>
+ <message>
+ <source>Library load failure</source>
+ <translation>库加载失败</translation>
+ </message>
+ <message>
+ <source>Error occurred while loading protocol %1
+
+%2
+</source>
+ <translation type="unfinished">加载åè®® %1 æ—¶å‘生错误
+
+%2
+</translation>
+ </message>
+ <message>
+ <source>Error occurred while loading filter %1
+
+%2
+</source>
+ <translation type="unfinished">加载滤波器 %1 æ—¶å‘生错误
+
+%2
+</translation>
+ </message>
+ <message>
+ <source>Error occurred while loading tracker %1
+
+%2
+</source>
+ <translation type="unfinished">加载跟踪器 %1 æ—¶å‘生错误
+
+%2
+</translation>
+ </message>
+ <message>
+ <source>Startup failure</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/logic/main-settings.cpp b/logic/main-settings.cpp
index c735ac6e..3865e602 100644
--- a/logic/main-settings.cpp
+++ b/logic/main-settings.cpp
@@ -1,62 +1,28 @@
#include "main-settings.hpp"
-using namespace options;
+namespace main_settings_impl {
-main_settings::main_settings() :
- b(make_bundle("opentrack-ui")),
- b_map(make_bundle("opentrack-mappings")),
- a_x("x", TX),
- a_y("y", TY),
- a_z("z", TZ),
- a_yaw("yaw", Yaw),
- a_pitch("pitch", Pitch),
- a_roll("roll", Roll),
- all_axis_opts { &a_x, &a_y, &a_z, &a_yaw, &a_pitch, &a_roll },
- reltrans_disable_tx(b, "compensate-translation-disable-x-axis", false),
- reltrans_disable_ty(b, "compensate-translation-disable-y-axis", false),
- reltrans_disable_tz(b, "compensate-translation-disable-z-axis", false),
- reltrans_disable_src_yaw(b, "compensate-translation-disable-source-yaw", false),
- reltrans_disable_src_pitch(b, "compensate-translation-disable-source-pitch", false),
- reltrans_disable_src_roll(b, "compensate-translation-disable-source-roll", false),
- tray_enabled(b, "use-system-tray", false),
- tray_start(b, "start-in-tray", false),
- center_at_startup(b, "center-at-startup", true),
- //center_method(b, "centering-method", 1),
- neck_z(b, "neck-depth", 0),
- neck_enable(b, "neck-enable", false),
- key_start_tracking1(b, "start-tracking"),
- key_start_tracking2(b, "start-tracking-alt"),
- key_stop_tracking1(b, "stop-tracking"),
- key_stop_tracking2(b, "stop-tracking-alt"),
- key_toggle_tracking1(b, "toggle-tracking"),
- key_toggle_tracking2(b, "toggle-tracking-alt"),
- key_restart_tracking1(b, "restart-tracking"),
- key_restart_tracking2(b, "restart-tracking-alt"),
- key_center1(b, "center"),
- key_center2(b, "center-alt"),
- key_toggle1(b, "toggle"),
- key_toggle2(b, "toggle-alt"),
- key_zero1(b, "zero"),
- key_zero2(b, "zero-alt"),
- key_toggle_press1(b, "toggle-press"),
- key_toggle_press2(b, "toggle-press-alt"),
- key_zero_press1(b, "zero-press"),
- key_zero_press2(b, "zero-press-alt"),
- tracklogging_enabled(b, "tracklogging-enabled", false),
- tracklogging_filename(b, "tracklogging-filename", QString())
-{
-}
+using namespace options;
-module_settings::module_settings() :
- b(make_bundle("modules")),
- tracker_dll(b, "tracker-dll", "PointTracker 1.1"),
- filter_dll(b, "filter-dll", "Accela"),
- protocol_dll(b, "protocol-dll", "freetrack 2.0 Enhanced")
-{
-}
+main_settings::main_settings() = default;
+module_settings::module_settings() = default;
key_opts::key_opts(bundle b, const QString& name) :
keycode(b, QString("keycode-%1").arg(name), ""),
guid(b, QString("guid-%1").arg(name), ""),
button(b, QString("button-%1").arg(name), -1)
{}
+
+key_opts& key_opts::operator=(const key_opts& x)
+{
+ if (&x != this)
+ {
+ keycode = x.keycode;
+ guid = x.guid;
+ button = x.button;
+ }
+
+ return *this;
+}
+
+} // ns main_settings_impl
diff --git a/logic/main-settings.hpp b/logic/main-settings.hpp
index df86f856..fcd5e745 100644
--- a/logic/main-settings.hpp
+++ b/logic/main-settings.hpp
@@ -15,13 +15,21 @@
#include "export.hpp"
-enum reltrans_state
+enum reltrans_state : int
{
reltrans_disabled = 0,
reltrans_enabled = 1,
reltrans_non_center = 2,
};
+enum centering_state : int
+{
+ center_disabled = 0,
+ center_point = 1,
+ center_vr360 = 2,
+ center_roll_compensated = 3,
+};
+
namespace main_settings_impl {
using namespace options;
@@ -32,46 +40,78 @@ struct OTR_LOGIC_EXPORT key_opts
value<int> button;
key_opts(bundle b, const QString& name);
+ key_opts& operator=(const key_opts& x);
};
struct OTR_LOGIC_EXPORT module_settings
{
- bundle b;
- value<QString> tracker_dll, filter_dll, protocol_dll;
+ bundle b { make_bundle("modules") };
+ value<QString> tracker_dll { b, "tracker-dll", "pt" };
+ value<QString> filter_dll { b, "filter-dll", "accela" };
+ value<QString> protocol_dll { b, "protocol-dll", "freetrack" };
module_settings();
};
struct OTR_LOGIC_EXPORT main_settings final
{
- bundle b, b_map;
- axis_opts a_x, a_y, a_z;
- axis_opts a_yaw, a_pitch, a_roll;
- axis_opts* all_axis_opts[6];
+ bundle b { make_bundle("opentrack-ui") };
+ bundle b_map { make_bundle("opentrack-mappings") };
+ axis_opts a_x{ "x", TX }, a_y { "y", TY }, a_z { "z", TZ };
+ axis_opts a_yaw{ "yaw", Yaw }, a_pitch { "pitch", Pitch }, a_roll { "roll", Roll };
+ axis_opts* all_axis_opts[6] { &a_x, &a_y, &a_z, &a_yaw, &a_pitch, &a_roll };
value<reltrans_state> reltrans_mode { b, "relative-translation-mode", reltrans_disabled };
- value<bool> reltrans_disable_tx, reltrans_disable_ty, reltrans_disable_tz;
- value<bool> reltrans_disable_src_yaw, reltrans_disable_src_pitch, reltrans_disable_src_roll;
- value<bool> tray_enabled, tray_start;
- value<bool> center_at_startup;
- //value<int> center_method;
- value<int> neck_z;
- value<bool> neck_enable;
- key_opts key_start_tracking1, key_start_tracking2;
- key_opts key_stop_tracking1, key_stop_tracking2;
- key_opts key_toggle_tracking1, key_toggle_tracking2;
- key_opts key_restart_tracking1, key_restart_tracking2;
- key_opts key_center1, key_center2;
- key_opts key_toggle1, key_toggle2;
- key_opts key_zero1, key_zero2;
- key_opts key_toggle_press1, key_toggle_press2;
- key_opts key_zero_press1, key_zero_press2;
- value<bool> tracklogging_enabled;
- value<QString> tracklogging_filename;
+
+ value<bool> reltrans_disable_tx { b, "compensate-translation-disable-x-axis", false };
+ value<bool> reltrans_disable_ty { b, "compensate-translation-disable-y-axis", false };
+ value<bool> reltrans_disable_tz { b, "compensate-translation-disable-z-axis", false };
+
+ value<bool> reltrans_disable_src_yaw { b, "compensate-translation-disable-source-yaw", false };
+ value<bool> reltrans_disable_src_pitch { b, "compensate-translation-disable-source-pitch", false };
+ value<bool> reltrans_disable_src_roll { b, "compensate-translation-disable-source-roll", false };
+
+ value<bool> tray_enabled { b, "use-system-tray", false };
+ value<bool> tray_start { b, "start-in-tray", false };
+
+ value<bool> center_at_startup { b, "center-at-startup", true };
+ value<centering_state> centering_mode { b, "centering-mode", center_roll_compensated };
+ value<int> neck_z { b, "neck-depth", 0 };
+ value<bool> neck_enable { b, "neck-enable", false };
+
+ key_opts key_start_tracking1 { b, "start-tracking" };
+ key_opts key_start_tracking2 { b, "start-tracking-alt" };
+
+ key_opts key_stop_tracking1 { b, "stop-tracking" };
+ key_opts key_stop_tracking2 { b, "stop-tracking-alt" };
+
+ key_opts key_toggle_tracking1 { b, "toggle-tracking" };
+ key_opts key_toggle_tracking2 { b, "toggle-tracking-alt" };
+
+ key_opts key_restart_tracking1 { b, "restart-tracking" };
+ key_opts key_restart_tracking2 { b, "restart-tracking-alt" };
+
+ key_opts key_center1 { b, "center" };
+ key_opts key_center2 { b, "center-alt" };
+
+ key_opts key_toggle1 { b, "toggle" };
+ key_opts key_toggle2 { b, "toggle-alt" };
+
+ key_opts key_zero1 { b, "zero" };
+ key_opts key_zero2 { b, "zero-alt" };
+
+ key_opts key_toggle_press1 { b, "toggle-press" };
+ key_opts key_toggle_press2 { b, "toggle-press-alt" };
+
+ key_opts key_zero_press1 { b, "zero-press" };
+ key_opts key_zero_press2 { b, "zero-press-alt" };
+
+ value<bool> tracklogging_enabled { b, "tracklogging-enabled", false };
+ value<QString> tracklogging_filename { b, "tracklogging-filename", {} };
main_settings();
};
} // ns main_settings_impl
-using main_settings_impl::key_opts;
-using main_settings_impl::module_settings;
-using main_settings_impl::main_settings;
+using key_opts = main_settings_impl::key_opts;
+using module_settings = main_settings_impl::module_settings;
+using main_settings = main_settings_impl::main_settings;
diff --git a/logic/mappings.hpp b/logic/mappings.hpp
index f88e6820..be7dd3a2 100644
--- a/logic/mappings.hpp
+++ b/logic/mappings.hpp
@@ -29,12 +29,10 @@ class OTR_LOGIC_EXPORT Mappings final
private:
Map axes[6];
public:
- Mappings(axis_opts** opts);
+ explicit Mappings(axis_opts** opts);
Map& operator()(int i) { return axes[i]; }
const Map& operator()(int i) const { return axes[i]; }
- Map& operator()(unsigned i) { return axes[i]; }
- const Map& operator()(unsigned i) const { return axes[i]; }
template<typename f> void forall(f&& fun)
{
diff --git a/logic/pipeline.cpp b/logic/pipeline.cpp
index 7774b02e..2e8efe55 100644
--- a/logic/pipeline.cpp
+++ b/logic/pipeline.cpp
@@ -15,7 +15,8 @@
#include "compat/sleep.hpp"
#include "compat/math.hpp"
#include "compat/meta.hpp"
-#include "compat/macros.hpp"
+#include "compat/macros.h"
+#include "compat/thread-name.hpp"
#include "pipeline.hpp"
#include "logic/shortcuts.h"
@@ -26,26 +27,34 @@
#ifdef _WIN32
# include <windows.h>
+# include <mmsystem.h>
#endif
-using namespace euler;
-using namespace time_units;
-using namespace gui_tracker_impl;
+//#define DEBUG_TIMINGS
+#ifdef DEBUG_TIMINGS
+# include "compat/variance.hpp"
+#endif
+
+namespace pipeline_impl {
-static constexpr inline double r2d = 180. / M_PI;
-static constexpr inline double d2r = M_PI / 180.;
+reltrans::reltrans() = default;
-reltrans::reltrans() {}
+void reltrans::on_center()
+{
+ interp_pos = { 0, 0, 0 };
+ in_zone = false;
+ moving_to_reltans = false;
+}
-euler_t reltrans::rotate(const rmat& R, const euler_t& in, vec3_bool disable) const
+Pose_ reltrans::rotate(const rmat& R, const Pose_& in, vec3_bool disable) const
{
enum { tb_Z, tb_X, tb_Y };
// TY is really yaw axis. need swapping accordingly.
// sign changes are due to right-vs-left handedness of coordinate system used
- const euler_t ret = R * euler_t(in(TZ), -in(TX), -in(TY));
+ const Pose_ ret = R * Pose_(in(TZ), -in(TX), -in(TY));
- euler_t output;
+ Pose_ output;
if (disable(TZ))
output(TZ) = in(TZ);
@@ -68,104 +77,92 @@ euler_t reltrans::rotate(const rmat& R, const euler_t& in, vec3_bool disable) co
Pose reltrans::apply_pipeline(reltrans_state state, const Pose& value,
const vec6_bool& disable, bool neck_enable, int neck_z)
{
- euler_t rel(static_cast<const double*>(value));
+ Pose_ rel((const double*)value);
if (state != reltrans_disabled)
{
+ bool in_zone_ = true;
+ if (state == reltrans_non_center)
{
- bool tcomp_in_zone_ = progn(
- if (state == reltrans_non_center)
- {
- const bool look_up = value(Pitch) < 15;
- return look_up ? std::fabs(value(Yaw)) > 85 : std::fabs(value(Yaw)) < 50;
- }
- else
- return true;
- );
-
- if (!cur && in_zone != tcomp_in_zone_)
- {
- //qDebug() << "reltrans-interp: START" << tcomp_in_zone_;
- cur = true;
- interp_timer.start();
- interp_phase_timer.start();
- RC_phase = 0;
- }
+ const bool looking_down = value(Pitch) < 20;
+ in_zone_ = looking_down ? std::fabs(value(Yaw)) > 35 : std::fabs(value(Yaw)) > 65;
+ }
- in_zone = tcomp_in_zone_;
+ if (!moving_to_reltans && in_zone != in_zone_)
+ {
+ //qDebug() << "reltrans-interp: START" << tcomp_in_zone_;
+ moving_to_reltans = true;
+ interp_timer.start();
+ interp_phase_timer.start();
+ RC_stage = 0;
}
+ in_zone = in_zone_;
+
// only when looking behind or downward
if (in_zone)
{
- const rmat R = euler_to_rmat(euler_t(value(Yaw) * d2r * !disable(Yaw),
- value(Pitch) * d2r * !disable(Pitch),
- value(Roll) * d2r * !disable(Roll)));
+ constexpr double d2r = M_PI / 180;
+
+ const rmat R = euler_to_rmat(
+ Pose_(value(Yaw) * d2r * !disable(Yaw),
+ value(Pitch) * d2r * !disable(Pitch),
+ value(Roll) * d2r * !disable(Roll)));
rel = rotate(R, rel, &disable[TX]);
// dynamic neck
- if (neck_enable)
+ if (neck_enable && (state != reltrans_non_center || !in_zone))
{
- const euler_t neck = apply_neck(value, -neck_z);
+ const Pose_ neck = apply_neck(R, -neck_z, disable(TZ));
for (unsigned k = 0; k < 3; k++)
rel(k) += neck(k);
}
}
- if (cur)
+ if (moving_to_reltans)
{
- static constexpr double RC_phases[] = { 2, 1, .5, .1, };
- static constexpr double RC_time_deltas[] = { 1, .25, 25., };
-
const double dt = interp_timer.elapsed_seconds();
+
+ static constexpr double RC_stages[] = { 2, 1, .5, .1, .05 };
+ static constexpr double RC_time_deltas[] = { 1, .25, .25, 2 };
+
interp_timer.start();
- if (RC_phase + 1 != std::size(RC_phases) &&
- interp_phase_timer.elapsed_seconds() > RC_time_deltas[RC_phase])
+ if (RC_stage + 1 < std::size(RC_stages) &&
+ interp_phase_timer.elapsed_seconds() > RC_time_deltas[RC_stage])
{
- RC_phase++;
+ RC_stage++;
interp_phase_timer.start();
}
- const double RC = RC_phases[RC_phase];
+ const double RC = RC_stages[RC_stage];
const double alpha = dt/(dt+RC);
- constexpr double eps = .05;
+ constexpr double eps = .01;
interp_pos = interp_pos * (1-alpha) + rel * alpha;
- const euler_t tmp = rel - interp_pos;
+ const Pose_ tmp = rel - interp_pos;
rel = interp_pos;
- const double delta = std::fabs(tmp(0)) + std::fabs(tmp(0)) + std::fabs(tmp(0));
+ const double delta = std::fabs(tmp(0)) + std::fabs(tmp(1)) + std::fabs(tmp(2));
- //qDebug() << "reltrans-interp: delta" << delta;
+ //qDebug() << "reltrans-interp: delta" << delta << "phase" << RC_phase;
if (delta < eps)
{
//qDebug() << "reltrans-interp: STOP";
- cur = false;
+ moving_to_reltans = false;
}
}
else
- {
interp_pos = rel;
- }
}
else
{
- cur = false;
+ moving_to_reltans = false;
in_zone = false;
-
- // dynamic neck
- if (neck_enable)
- {
- const euler_t neck = apply_neck(value, -neck_z);
-
- for (unsigned k = 0; k < 3; k++)
- rel(k) += neck(k);
- }
}
return {
@@ -174,22 +171,21 @@ Pose reltrans::apply_pipeline(reltrans_state state, const Pose& value,
};
}
-euler_t reltrans::apply_neck(const Pose& value, int nz) const
+Pose_ reltrans::apply_neck(const rmat& R, int nz, bool disable_tz) const
{
- euler_t neck;
+ Pose_ neck;
- const rmat R = euler_to_rmat(euler_t(&value[Yaw]) * d2r);
- neck = rotate(R, { 0, 0, nz }, vec3_bool());
+ neck = rotate(R, { 0, 0, nz }, {});
neck(TZ) = neck(TZ) - nz;
+ if (disable_tz)
+ neck(TZ) = 0;
+
return neck;
}
-pipeline::pipeline(Mappings& m, runtime_libraries& libs, event_handler& ev, TrackLogger& logger) :
- m(m),
- ev(ev),
- libs(libs),
- logger(logger)
+pipeline::pipeline(const Mappings& m, const runtime_libraries& libs, TrackLogger& logger) :
+ m(m), libs(libs), logger(logger)
{
}
@@ -199,20 +195,20 @@ pipeline::~pipeline()
wait();
}
-double pipeline::map(double pos, Map& axis)
+double pipeline::map(double pos, const Map& axis)
{
bool altp = (pos < 0) && axis.opts.altp;
- axis.spline_main.set_tracking_active( !altp );
- axis.spline_alt.set_tracking_active( altp );
+ axis.spline_main.set_tracking_active(!altp);
+ axis.spline_alt.set_tracking_active(altp);
auto& fc = altp ? axis.spline_alt : axis.spline_main;
- return double(fc.get_value(pos));
+ return fc.get_value(pos);
}
template<int u, int w>
-static bool is_nan(const dmat<u,w>& r)
+static inline bool is_nan(const dmat<u,w>& r)
{
- for (int i = 0; i < u; i++)
- for (int j = 0; j < w; j++)
+ for (unsigned i = 0; i < u; i++)
+ for (unsigned j = 0; j < w; j++)
{
int val = std::fpclassify(r(i, j));
if (val == FP_NAN || val == FP_INFINITE)
@@ -222,29 +218,14 @@ static bool is_nan(const dmat<u,w>& r)
return false;
}
-template<typename x, typename y, typename... xs>
-static force_inline bool nan_check_(const x& datum, const y& next, const xs&... rest)
-{
- return is_nan(datum) || nan_check_(next, rest...);
-}
-
-template<typename x>
-static force_inline bool nan_check_(const x& datum)
-{
- return is_nan(datum);
-}
-
-template<typename>
-static bool nan_check_() = delete;
-
static never_inline
void emit_nan_check_msg(const char* text, const char* fun, int line)
{
- once_only(
+ eval_once(
qDebug() << "nan check failed"
<< "for:" << text
<< "function:" << fun
- << "line:" << line;
+ << "line:" << line
);
}
@@ -252,24 +233,23 @@ template<typename... xs>
static never_inline
bool maybe_nan(const char* text, const char* fun, int line, const xs&... vals)
{
- if (nan_check_(vals...))
- {
+ bool ret = (is_nan(vals) || ... || false);
+
+ if (ret)
emit_nan_check_msg(text, fun, line);
- return true;
- }
- return false;
-}
-// for MSVC `else' is like `unlikely' for GNU
+ return ret;
+}
-#define nan_check(...) \
- do \
- { \
- if (likely(!maybe_nan(#__VA_ARGS__, OTR_FUNNAME, __LINE__, __VA_ARGS__))) \
- (void)0; \
- else \
- goto error; \
- } while (false)
+#define nan_check(...) \
+ do \
+ { \
+ if (likely(!maybe_nan(#__VA_ARGS__, function_name, __LINE__, __VA_ARGS__))) \
+ (void)0; \
+ else \
+ goto error; \
+ } \
+ while (false)
bool pipeline::maybe_enable_center_on_tracking_started()
{
@@ -284,7 +264,7 @@ bool pipeline::maybe_enable_center_on_tracking_started()
if (tracking_started && s.center_at_startup)
{
- set(f_center, true);
+ set_center(true);
return true;
}
}
@@ -292,84 +272,91 @@ bool pipeline::maybe_enable_center_on_tracking_started()
return false;
}
-void pipeline::maybe_set_center_pose(const Pose& value, bool own_center_logic)
+void pipeline::maybe_set_center_pose(const centering_state mode, const Pose& value, bool own_center_logic)
{
- euler_t tmp = d2r * euler_t(&value[Yaw]);
- //scaled_rotation.rotation = euler_to_rmat(c_div * tmp);
- rotation.rotation = euler_to_rmat(tmp);
-
- if (get(f_center))
+ if (b.get(f_center | f_held_center))
{
+ set_center(false);
+
if (libs.pFilter)
libs.pFilter->center();
if (own_center_logic)
{
- //scaled_rotation.rot_center = rmat::eye();
- rotation.inv_rot_center = rmat::eye();
-
- t_center = euler_t();
+ center.P = {};
+ center.QC = QQuaternion(1,0,0,0);
+ center.QR = QQuaternion(1,0,0,0);
}
else
{
- rotation.inv_rot_center = rotation.rotation.t();
- //scaled_rotation.rot_center = scaled_rotation.rotation.t();
-
- t_center = euler_t(static_cast<const double*>(value));
+ if (mode)
+ {
+ center.P = value;
+ center.QC = QQuaternion::fromEulerAngles(value[Pitch], value[Yaw], -value[Roll]).conjugated();
+ center.QR = QQuaternion::fromEulerAngles(value[Pitch], value[Yaw], 0).conjugated();
+ }
+ else
+ {
+ // To reset the centering coordinates
+ // just select "Centering method [Disabled]" and then press [Center] button
+ center.P = {};
+ center.QC = QQuaternion(1,0,0,0);
+ center.QR = QQuaternion(1,0,0,0);
+ }
}
}
}
-Pose pipeline::clamp_value(Pose value) const
+Pose pipeline::apply_center(const centering_state mode, Pose value) const
{
- // hatire, udp, and freepie trackers can mess up here
- for (unsigned i = 3; i < 6; i++)
- {
- using std::fmod;
- using std::copysign;
- using std::fabs;
+ if (mode != center_disabled)
+ {
+ for (unsigned k = TX; k <= TZ; k++)
+ value(k) -= center.P(k);
- value(i) = fmod(value(i), 360);
+ QQuaternion q;
+ QVector3D v;
- const double x = value(i);
- if (fabs(x) - 1e-2 > 180)
- value(i) = fmod(x + copysign(180, x), 360) - copysign(180, x);
- else
- value(i) = clamp(x, -180, 180);
+ switch (mode)
+ {
+ case center_point:
+ for (unsigned k = Yaw; k <= Roll; k++)
+ {
+ value(k) -= center.P(k);
+ if (fabs(value[k]) > 180) value[k] -= copysign(360, value[k]);
+ }
+ break;
+ case center_vr360:
+ q = QQuaternion::fromEulerAngles(value[Pitch], value[Yaw], -value[Roll]);
+ q = center.QC * q;
+ v = q.toEulerAngles();
+ value[Pitch] = v.x();
+ value[Yaw] = v.y();
+ value[Roll] = -v.z();
+ break;
+ case center_roll_compensated:
+ q = QQuaternion::fromEulerAngles(value[Pitch], value[Yaw], center.P[Roll] - value[Roll]);
+ q = center.QR * q;
+ v = q.toEulerAngles();
+ value[Pitch] = v.x();
+ value[Yaw] = v.y();
+ value[Roll] = -v.z();
+ break;
+ case center_disabled: break;
+ }
}
- return value;
-}
-
-Pose pipeline::apply_center(Pose value) const
-{
- euler_t T = euler_t(value) - t_center;
- euler_t R = r2d * rmat_to_euler(rotation.rotation * rotation.inv_rot_center);
-
- T = rel.rotate(rotation.inv_rot_center, T, vec3_bool());
-
- for (int i = 0; i < 3; i++)
- {
+ for (int i = 0; i < 6; i++)
// don't invert after reltrans
// inverting here doesn't break centering
-
- if (m(i+3).opts.invert)
- R(i) = -R(i);
- if (m(i).opts.invert)
- T(i) = -T(i);
- }
-
- for (int i = 0; i < 3; i++)
- {
- value(i) = T(i);
- value(i+3) = R(i);
- }
+ if (m(i).opts.invert_pre)
+ value(i) = -value(i);
return value;
}
std::tuple<Pose, Pose, vec6_bool>
-pipeline::get_selected_axis_value(const Pose& newpose) const
+pipeline::get_selected_axis_values(const Pose& newpose) const
{
Pose value;
vec6_bool disabled;
@@ -403,22 +390,24 @@ Pose pipeline::maybe_apply_filter(const Pose& value) const
Pose pipeline::apply_zero_pos(Pose value) const
{
- // custom zero position
for (int i = 0; i < 6; i++)
- value(i) += m(i).opts.zero * (m(i).opts.invert ? -1 : 1);
+ value(i) += m(i).opts.zero * (m(i).opts.invert_pre ? -1 : 1);
return value;
}
-Pose pipeline::apply_reltrans(Pose value, vec6_bool disabled)
+Pose pipeline::apply_reltrans(Pose value, vec6_bool disabled, bool centerp)
{
+ if (centerp)
+ rel.on_center();
+
value = rel.apply_pipeline(s.reltrans_mode, value,
- { !!s.reltrans_disable_src_yaw,
- !!s.reltrans_disable_src_pitch,
- !!s.reltrans_disable_src_roll,
- !!s.reltrans_disable_tx,
+ { !!s.reltrans_disable_tx,
!!s.reltrans_disable_ty,
- !!s.reltrans_disable_tz },
+ !!s.reltrans_disable_tz,
+ !!s.reltrans_disable_src_yaw,
+ !!s.reltrans_disable_src_pitch,
+ !!s.reltrans_disable_src_roll, },
s.neck_enable,
s.neck_z);
@@ -433,57 +422,53 @@ Pose pipeline::apply_reltrans(Pose value, vec6_bool disabled)
void pipeline::logic()
{
using namespace euler;
- using EV = event_handler::event_ordinal;
logger.write_dt();
logger.reset_dt();
// we must center prior to getting data from the tracker
- const bool center_ordered = get(f_center) && tracking_started;
+ const bool center_ordered = b.get(f_center | f_held_center) && tracking_started;
const bool own_center_logic = center_ordered && libs.pTracker->center();
-
- Pose value, raw;
- vec6_bool disabled;
+ const bool hold_ordered = b.get(f_enabled_p) ^ b.get(f_enabled_h);
{
Pose tmp;
libs.pTracker->data(tmp);
- nan_check(tmp);
- ev.run_events(EV::ev_raw, tmp);
-
- if (get(f_enabled_p) ^ !get(f_enabled_h))
- for (int i = 0; i < 6; i++)
- newpose(i) = tmp(i);
+ newpose = tmp;
}
- std::tie(raw, value, disabled) = get_selected_axis_value(newpose);
+ auto [raw, value, disabled] = get_selected_axis_values(newpose);
logger.write_pose(raw); // raw
- value = clamp_value(value);
+ nan_check(newpose, raw, value);
{
maybe_enable_center_on_tracking_started();
- maybe_set_center_pose(value, own_center_logic);
- value = apply_center(value);
+ maybe_set_center_pose(s.centering_mode, value, own_center_logic);
+ value = apply_center(s.centering_mode, value);
+
// "corrected" - after various transformations to account for camera position
logger.write_pose(value);
}
{
- ev.run_events(EV::ev_before_filter, value);
- value = maybe_apply_filter(value);
+ // we must proceed with all the filtering since the filter
+ // needs fresh values to prevent deconvergence
+ if (center_ordered)
+ (void)maybe_apply_filter(value);
+ else
+ value = maybe_apply_filter(value);
nan_check(value);
logger.write_pose(value); // "filtered"
}
{
- ev.run_events(EV::ev_before_mapping, value);
- // CAVEAT rotation only, due to tcomp
+ // CAVEAT rotation only, due to reltrans
for (int i = 3; i < 6; i++)
value(i) = map(value(i), m(i));
}
- value = apply_reltrans(value, disabled);
+ value = apply_reltrans(value, disabled, center_ordered);
{
// CAVEAT translation only, due to tcomp
@@ -498,7 +483,7 @@ error:
{
QMutexLocker foo(&mtx);
- value = output_pose;
+ value = last_value;
raw = raw_6dof;
// for widget last value display
@@ -508,16 +493,22 @@ error:
ok:
- set(f_center, false);
+ set_center(false);
- if (get(f_zero))
+ if (b.get(f_zero))
for (int i = 0; i < 6; i++)
value(i) = 0;
+ if (hold_ordered)
+ value = last_value;
+ last_value = value;
value = apply_zero_pos(value);
- ev.run_events(EV::ev_finished, value);
- libs.pProtocol->pose(value);
+ for (int i = 0; i < 6; i++)
+ if (m(i).opts.invert_post)
+ value(i) = -value(i);
+
+ libs.pProtocol->pose(value, raw);
QMutexLocker foo(&mtx);
output_pose = value;
@@ -529,21 +520,63 @@ ok:
logger.next_line();
}
+#ifdef DEBUG_TIMINGS
+static void debug_timings(float backlog_time, int sleep_time)
+{
+ static variance v, v2;
+ static Timer t, t2;
+ static unsigned cnt, k;
+
+ if (k > 1000)
+ {
+ v.input((double)backlog_time);
+ v2.input(sleep_time);
+
+ if (t.elapsed_ms() >= 1000)
+ {
+ t.start();
+ qDebug() << cnt << "Hz:"
+ << "backlog"
+ << "avg" << v.avg()
+ << "dev" << v.stddev()
+ << "| sleep" << v2.avg()
+ << "dev" << v2.stddev();
+
+ cnt = 0;
+ }
+
+ cnt++;
+ }
+ else
+ {
+ k++;
+ t.start();
+ }
+
+ t2.start();
+}
+#endif
+
void pipeline::run()
{
+ portable::set_curthread_name("tracking pipeline");
+
#if defined _WIN32
const MMRESULT mmres = timeBeginPeriod(1);
#endif
+ setPriority(QThread::HighPriority);
+ setPriority(QThread::HighestPriority);
+
{
static const char* const posechannels[6] = { "TX", "TY", "TZ", "Yaw", "Pitch", "Roll" };
static const char* const datachannels[5] = { "dt", "raw", "corrected", "filtered", "mapped" };
logger.write(datachannels[0]);
- char buffer[128];
- for (unsigned j = 1; j < 5; ++j)
+ char buffer[16];
+ for (unsigned j = 1; j < 5; ++j) // NOLINT(modernize-loop-convert)
{
- for (unsigned i = 0; i < 6; ++i)
+ for (unsigned i = 0; i < 6; ++i) // NOLINT(modernize-loop-convert)
{
std::sprintf(buffer, "%s%s", datachannels[j], posechannels[i]);
logger.write(buffer);
@@ -560,33 +593,28 @@ void pipeline::run()
{
logic();
- constexpr ns const_sleep_ms(time_cast<ns>(ms(4)));
- const ns elapsed_nsecs = prog1(t.elapsed<ns>(), t.start());
+ constexpr ms interval{4};
+ backlog_time += ms{t.elapsed_ms()} - interval;
+ t.start();
- if (backlog_time > secs_(3) || backlog_time < secs_(-3))
+ if (std::chrono::abs(backlog_time) > secs(3))
{
qDebug() << "tracker: backlog interval overflow"
- << time_cast<ms>(backlog_time).count() << "ms";
- backlog_time = backlog_time.zero();
+ << ms{backlog_time}.count() << "ms";
+ backlog_time = {};
}
- backlog_time += ns(elapsed_nsecs - const_sleep_ms);
-
- const int sleep_time_ms = time_cast<ms>(clamp(const_sleep_ms - backlog_time,
- ms::zero(), ms(10))).count();
+ const int sleep_ms = (int)std::clamp(interval - backlog_time, ms{0}, ms{10}).count();
-#if 0
- qDebug() << "sleepy time" << sleep_time_ms
- << "elapsed" << time_cast<ms>(elapsed_nsecs).count()
- << "backlog" << time_cast<ms>(backlog_time).count();
+#ifdef DEBUG_TIMINGS
+ debug_timings(backlog_time.count(), sleep_ms);
#endif
-
- portable::sleep(sleep_time_ms);
+ portable::sleep(sleep_ms);
}
// filter may inhibit exact origin
Pose p;
- libs.pProtocol->pose(p);
+ libs.pProtocol->pose(p, p);
for (int i = 0; i < 6; i++)
{
@@ -602,7 +630,7 @@ void pipeline::run()
void pipeline::raw_and_mapped_pose(double* mapped, double* raw) const
{
- QMutexLocker foo(&const_cast<pipeline&>(*this).mtx);
+ QMutexLocker foo(&mtx);
for (int i = 0; i < 6; i++)
{
@@ -611,55 +639,52 @@ void pipeline::raw_and_mapped_pose(double* mapped, double* raw) const
}
}
-void pipeline::set_center() { set(f_center, true); }
+void pipeline::set_center(bool x) { b.set(f_center, x); }
-void pipeline::set_enabled(bool value) { set(f_enabled_h, value); }
-void pipeline::set_zero(bool value) { set(f_zero, value); }
+void pipeline::set_held_center(bool value)
+{
+ if (value)
+ b.set(f_held_center | f_center, true);
+ else
+ b.set(f_held_center, false);
+}
-void pipeline::toggle_zero() { negate(f_zero); }
-void pipeline::toggle_enabled() { negate(f_enabled_p); }
+void pipeline::set_enabled(bool value) { b.set(f_enabled_h, value); }
+void pipeline::set_zero(bool value) { b.set(f_zero, value); }
-void bits::set(flags flag_, bool val_)
+void pipeline::toggle_zero() { b.negate(f_zero); }
+void pipeline::toggle_enabled() { b.negate(f_enabled_p); }
+
+void bits::set(bit_flags flag, bool val)
{
- const unsigned flag = unsigned(flag_);
- const unsigned val = unsigned(val_);
+ QMutexLocker l(&lock);
- for (;;)
- {
- unsigned b_(b);
- if (b.compare_exchange_weak(b_,
- unsigned((b_ & ~flag) | (flag * val)),
- std::memory_order_seq_cst,
- std::memory_order_seq_cst))
- break;
- }
+ flags &= ~flag;
+ if (val)
+ flags |= flag;
}
-void bits::negate(flags flag_)
+void bits::negate(bit_flags flag)
{
- const unsigned flag = unsigned(flag_);
+ QMutexLocker l(&lock);
- for (;;)
- {
- unsigned b_(b);
-
- if (b.compare_exchange_weak(b_,
- b_ ^ flag,
- std::memory_order_seq_cst,
- std::memory_order_seq_cst))
- break;
- }
+ flags ^= flag;
}
-bool bits::get(flags flag)
+bool bits::get(bit_flags flag)
{
- return !!(b & flag);
+ QMutexLocker l(&lock);
+
+ return !!(flags & flag);
}
-bits::bits() : b(0u)
+bits::bits()
{
- set(f_center, true);
+ set(f_center, false);
+ set(f_held_center, false);
set(f_enabled_p, true);
set(f_enabled_h, true);
set(f_zero, false);
}
+
+} // ns pipeline_impl
diff --git a/logic/pipeline.hpp b/logic/pipeline.hpp
index 82a339a3..a539525d 100644
--- a/logic/pipeline.hpp
+++ b/logic/pipeline.hpp
@@ -1,11 +1,3 @@
-/* Copyright (c) 2014-2015, Stanislaw Halik <sthalik@misaki.pl>
-
- * 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 <vector>
@@ -14,8 +6,8 @@
#include "api/plugin-support.hpp"
#include "mappings.hpp"
#include "compat/euler.hpp"
+#include "compat/enum-operators.hpp"
#include "runtime-libraries.hpp"
-#include "extensions.hpp"
#include "spline/spline.hpp"
#include "main-settings.hpp"
@@ -27,74 +19,74 @@
#include <atomic>
#include <cmath>
+#include <QQuaternion>
#include "export.hpp"
-namespace gui_tracker_impl {
+namespace pipeline_impl {
-using rmat = euler::rmat;
-using euler_t = euler::euler_t;
+using namespace euler;
+using namespace time_units;
using vec6_bool = Mat<bool, 6, 1>;
using vec3_bool = Mat<bool, 6, 1>;
class reltrans
{
- euler_t interp_pos, last_value;
+ Pose_ interp_pos;
Timer interp_timer;
// this implements smooth transition into reltrans mode
// once not aiming anymore. see `apply_pipeline()'.
Timer interp_phase_timer;
- unsigned RC_phase;
+ unsigned RC_stage = 0;
- bool cur = false;
+ bool moving_to_reltans = false;
bool in_zone = false;
public:
reltrans();
- warn_result_unused
- euler_t rotate(const rmat& rmat, const euler_t& in, vec3_bool disable) const;
+ void on_center();
- warn_result_unused
+ Pose_ rotate(const rmat& rmat, const Pose_& in, vec3_bool disable) const;
+ Pose_ apply_neck(const rmat& R, int nz, bool disable_tz) const;
Pose apply_pipeline(reltrans_state state, const Pose& value,
const vec6_bool& disable, bool neck_enable, int neck_z);
-
- warn_result_unused
- euler_t apply_neck(const Pose& value, int nz) const;
};
-using namespace time_units;
+enum bit_flags : unsigned {
+ f_none = 0,
+ f_center = 1 << 0,
+ f_held_center = 1 << 1,
+ f_enabled_h = 1 << 2,
+ f_enabled_p = 1 << 3,
+ f_zero = 1 << 4,
+};
struct OTR_LOGIC_EXPORT bits
{
- enum flags : unsigned {
- f_center = 1 << 0,
- f_enabled_h = 1 << 1,
- f_enabled_p = 1 << 2,
- f_zero = 1 << 3,
- };
-
- std::atomic<unsigned> b;
-
- void set(flags flag_, bool val_);
- void negate(flags flag_);
- bool get(flags flag);
+ bit_flags flags{0};
+ QMutex lock;
+
+ void set(bit_flags flag, bool val);
+ void negate(bit_flags flag);
+ bool get(bit_flags flag);
bits();
};
-class OTR_LOGIC_EXPORT pipeline : private QThread, private bits
+DEFINE_ENUM_OPERATORS(bit_flags);
+
+class OTR_LOGIC_EXPORT pipeline : private QThread
{
Q_OBJECT
-private:
- QMutex mtx;
+
+ mutable QMutex mtx;
main_settings s;
- Mappings& m;
- event_handler& ev;
+ const Mappings& m;
Timer t;
- Pose output_pose, raw_6dof, last_mapped, last_raw;
+ Pose output_pose, raw_6dof, last_value;
Pose newpose;
runtime_libraries const& libs;
@@ -103,42 +95,36 @@ private:
// the logger while the tracker is running.
TrackLogger& logger;
- struct state
- {
- rmat inv_rot_center;
- rmat rotation;
-
- state() : inv_rot_center(rmat::eye())
- {}
- };
-
reltrans rel;
- state rotation;
- euler_t t_center;
+ struct {
+ Pose P;
+ QQuaternion QC = QQuaternion(1,0,0,0);
+ QQuaternion QR = QQuaternion(1,0,0,0);
+ } center;
- ns backlog_time = ns(0);
+ time_units::ms backlog_time {};
bool tracking_started = false;
- double map(double pos, Map& axis);
+ static double map(double pos, const Map& axis);
void logic();
void run() override;
bool maybe_enable_center_on_tracking_started();
- void maybe_set_center_pose(const Pose& value, bool own_center_logic);
- Pose clamp_value(Pose value) const;
- Pose apply_center(Pose value) const;
- std::tuple<Pose, Pose, vec6_bool> get_selected_axis_value(const Pose& newpose) const;
+ void maybe_set_center_pose(const centering_state mode, const Pose& value, bool own_center_logic);
+ Pose apply_center(const centering_state mode, Pose value) const;
+ std::tuple<Pose, Pose, vec6_bool> get_selected_axis_values(const Pose& newpose) const;
Pose maybe_apply_filter(const Pose& value) const;
- Pose apply_reltrans(Pose value, vec6_bool disabled);
+ Pose apply_reltrans(Pose value, vec6_bool disabled, bool centerp);
Pose apply_zero_pos(Pose value) const;
- // reminder: float exponent base is 2
- //static constexpr inline double c_mult = 16;
- //static constexpr inline double c_div = 1./c_mult;
+ void set_center(bool x);
+
+ bits b;
+
public:
- pipeline(Mappings& m, runtime_libraries& libs, event_handler& ev, TrackLogger& logger);
- ~pipeline();
+ pipeline(const Mappings& m, const runtime_libraries& libs, TrackLogger& logger);
+ ~pipeline() override;
void raw_and_mapped_pose(double* mapped, double* raw) const;
void start() { QThread::start(QThread::HighPriority); }
@@ -146,11 +132,11 @@ public:
void toggle_zero();
void toggle_enabled();
- void set_center();
+ void set_held_center(bool value);
void set_enabled(bool value);
void set_zero(bool value);
};
-} // ns impl
+} // ns pipeline_impl
-using gui_tracker_impl::pipeline;
+using pipeline = pipeline_impl::pipeline;
diff --git a/logic/runtime-libraries.cpp b/logic/runtime-libraries.cpp
index d05e90c2..754f52cd 100644
--- a/logic/runtime-libraries.cpp
+++ b/logic/runtime-libraries.cpp
@@ -3,10 +3,16 @@
#include <QMessageBox>
#include <QDebug>
+#ifdef __clang__
+# pragma clang diagnostic ignored "-Wcomma"
+#endif
+
runtime_libraries::runtime_libraries(QFrame* frame, dylibptr t, dylibptr p, dylibptr f)
{
+ auto error = [](const QString& msg) { return module_status_mixin::error(msg); };
+
module_status status =
- module_status_mixin::error(otr_tr("Library load failure"));
+ error(tr("Library load failure"));
using namespace options;
@@ -15,12 +21,15 @@ runtime_libraries::runtime_libraries(QFrame* frame, dylibptr t, dylibptr p, dyli
pProtocol = make_dylib_instance<IProtocol>(p);
if (!pProtocol)
+ {
+ qDebug() << "protocol dylib load failure";
goto end;
+ }
if(status = pProtocol->initialize(), !status.is_ok())
{
- status = _("Error occurred while loading protocol %1\n\n%2\n")
- .arg(p->name).arg(status.error);
+ status = error(tr("Error occurred while loading protocol %1\n\n%2\n")
+ .arg(p->name, status.error));
goto end;
}
@@ -33,18 +42,24 @@ runtime_libraries::runtime_libraries(QFrame* frame, dylibptr t, dylibptr p, dyli
goto end;
}
+ if (f && f->Constructor && !pFilter)
+ {
+ qDebug() << "filter load failure";
+ goto end;
+ }
+
if (pFilter)
if(status = pFilter->initialize(), !status.is_ok())
{
- status = _("Error occurred while loading filter %1\n\n%2\n")
- .arg(f->name).arg(status.error);
+ status = error(tr("Error occurred while loading filter %1\n\n%2\n")
+ .arg(f->name, status.error));
goto end;
}
if (status = pTracker->start_tracker(frame), !status.is_ok())
{
- status = _("Error occurred while loading tracker %1\n\n%2\n")
- .arg(t->name).arg(status.error);
+ status = error(tr("Error occurred while loading tracker %1\n\n%2\n")
+ .arg(t->name, status.error));
goto end;
}
@@ -57,6 +72,8 @@ end:
pProtocol = nullptr;
if (!status.is_ok())
- QMessageBox::critical(nullptr, "Startup failure", status.error, QMessageBox::Cancel, QMessageBox::NoButton);
+ QMessageBox::critical(nullptr,
+ tr("Startup failure"), status.error,
+ QMessageBox::Cancel, QMessageBox::NoButton);
}
diff --git a/logic/runtime-libraries.hpp b/logic/runtime-libraries.hpp
index 1105c179..8c7fedd1 100644
--- a/logic/runtime-libraries.hpp
+++ b/logic/runtime-libraries.hpp
@@ -9,12 +9,16 @@
#pragma once
#include "api/plugin-support.hpp"
+#include "compat/tr.hpp"
#include "export.hpp"
-#include <QFrame>
+class QFrame;
-struct OTR_LOGIC_EXPORT runtime_libraries final
+class OTR_LOGIC_EXPORT runtime_libraries final : public TR
{
+ Q_OBJECT
+
+public:
using dylibptr = std::shared_ptr<dylib>;
std::shared_ptr<ITracker> pTracker;
@@ -22,7 +26,7 @@ struct OTR_LOGIC_EXPORT runtime_libraries final
std::shared_ptr<IProtocol> pProtocol;
runtime_libraries(QFrame* frame, dylibptr t, dylibptr p, dylibptr f);
- runtime_libraries() : pTracker(nullptr), pFilter(nullptr), pProtocol(nullptr), correct(false) {}
+ runtime_libraries() = default;
bool correct = false;
};
diff --git a/logic/shortcuts.cpp b/logic/shortcuts.cpp
index 5ddefc1f..df21f7d2 100644
--- a/logic/shortcuts.cpp
+++ b/logic/shortcuts.cpp
@@ -29,9 +29,9 @@ void Shortcuts::free_binding(K& key)
#endif
}
-void Shortcuts::bind_shortcut(K &key, const key_opts& k, bool held)
+void Shortcuts::bind_shortcut(K& key, const key_opts& k, bool held)
{
-#if !defined(_WIN32)
+#if !defined _WIN32
(void)held;
using sh = QxtGlobalShortcut;
if (key)
@@ -46,13 +46,18 @@ void Shortcuts::bind_shortcut(K &key, const key_opts& k, bool held)
key->setShortcut(QKeySequence::fromString(k.keycode, QKeySequence::PortableText));
key->setEnabled();
}
-}
#else
- key = K();
+ key = {};
int idx = 0;
- QKeySequence code;
+ QKeySequence code(QKeySequence::UnknownKey);
- if (k.guid != "")
+ if (k.guid == QStringLiteral("mouse"))
+ {
+ key.guid = k.guid;
+ key.keycode = k.button;
+ key.held = held;
+ }
+ if (!k.guid->isEmpty())
{
key.guid = k.guid;
key.keycode = k.button & ~Qt::KeyboardModifierMask;
@@ -63,48 +68,51 @@ void Shortcuts::bind_shortcut(K &key, const key_opts& k, bool held)
}
else
{
- if (k.keycode == "")
- code = QKeySequence(Qt::Key_unknown);
- else
+ if (!k.keycode->isEmpty())
code = QKeySequence::fromString(k.keycode, QKeySequence::PortableText);
Qt::KeyboardModifiers mods = Qt::NoModifier;
- if (code != Qt::Key_unknown)
- win_key::from_qt(code, idx, mods);
-
- key.guid = "";
- key.keycode = idx;
- key.held = held;
- key.ctrl = !!(mods & Qt::ControlModifier);
- key.alt = !!(mods & Qt::AltModifier);
- key.shift = !!(mods & Qt::ShiftModifier);
+ if (!code.isEmpty() &&
+ code != QKeySequence{ QKeySequence::UnknownKey } &&
+ win_key::from_qt(code, idx, mods))
+ {
+ key.guid = QString{};
+ key.keycode = idx;
+ key.held = held;
+ key.ctrl = !!(mods & Qt::ControlModifier);
+ key.alt = !!(mods & Qt::AltModifier);
+ key.shift = !!(mods & Qt::ShiftModifier);
+ }
}
-}
#endif
+}
#ifdef _WIN32
-void Shortcuts::receiver(const Key& k)
+
+void Shortcuts::receiver(const Key& key)
{
const unsigned sz = keys.size();
for (unsigned i = 0; i < sz; i++)
{
- K& k_ = std::get<0>(keys[i]);
- if (k.guid != k_.guid)
+ auto& [k, f, held] = keys[i];
+ if (key.guid != k.guid)
continue;
- if (k.keycode != k_.keycode)
+ if (key.keycode != k.keycode)
continue;
- if (k_.held && !k.held) continue;
- if (k_.alt != k.alt) continue;
- if (k_.ctrl != k.ctrl) continue;
- if (k_.shift != k.shift) continue;
- if (!k_.should_process())
- continue;
-
- fun& f = std::get<1>(keys[i]);
- f(k.held);
+ if (k.held && !key.held) continue;
+ if (key.held)
+ {
+ if (k.alt != key.alt) continue;
+ if (k.ctrl != key.ctrl) continue;
+ if (k.shift != key.shift) continue;
+ if (!k.should_process())
+ continue;
+ }
+ f(key.held);
}
}
+
#endif
Shortcuts::~Shortcuts()
@@ -127,24 +135,22 @@ void Shortcuts::reload(const t_keys& keys_)
for (unsigned i = 0; i < sz; i++)
{
- const auto& kk = keys_[i];
- const key_opts& opts = std::get<0>(kk);
- const bool held = std::get<2>(kk);
- auto fun = std::get<1>(kk);
+ auto const&[opts, fun, held] = keys_[i];
#ifdef _WIN32
K k;
#else
K k(nullptr);
#endif
bind_shortcut(k, opts, held);
- keys.push_back(tt(k, [=](bool flag) { fun(flag); }, held));
+ keys.emplace_back(k, fun, held);
#ifndef _WIN32
const int idx = keys.size() - 1;
tt& kk_ = keys[idx];
auto fn = std::get<1>(kk_);
- connect(k, &QxtGlobalShortcut::activated, [fn, held](bool keydown) {
- if (keydown || !held)
+ bool held_ = held;
+ connect(k, &QxtGlobalShortcut::activated, [fn, held_](bool keydown) {
+ if (keydown || !held_)
fn(keydown);
});
#endif
diff --git a/logic/shortcuts.h b/logic/shortcuts.h
index 046e68de..87926f66 100644
--- a/logic/shortcuts.h
+++ b/logic/shortcuts.h
@@ -24,6 +24,10 @@
#include <vector>
#include <functional>
+#if !defined __APPLE__
+# define OTR_HAS_KEY_UP_SUPPORT
+#endif
+
namespace shortcuts_impl {
using namespace options;
@@ -54,8 +58,8 @@ public:
KeybindingWorker::Token key_token {[this](const Key& k) { receiver(k); }};
#endif
- Shortcuts() {}
- ~Shortcuts();
+ Shortcuts() = default;
+ ~Shortcuts() override;
void reload(const t_keys& keys_);
private:
diff --git a/logic/state.cpp b/logic/state.cpp
new file mode 100644
index 00000000..dc5e5a36
--- /dev/null
+++ b/logic/state.cpp
@@ -0,0 +1,57 @@
+#include "state.hpp"
+#include "opentrack/defs.hpp"
+#include <iterator>
+
+using dylib_ptr = Modules::dylib_ptr;
+using dylib_list = Modules::dylib_list;
+
+std::tuple<dylib_ptr, int> State::module_by_name(const QString& name, dylib_list& list)
+{
+ auto it = std::find_if(list.cbegin(), list.cend(), [&name](const dylib_ptr& lib) {
+ if (!lib)
+ return name.isEmpty();
+ else
+ return name == lib->module_name;
+ });
+
+ if (it == list.cend())
+ return { nullptr, -1 };
+ else
+ return { *it, int(std::distance(list.cbegin(), it)) };
+}
+
+State::State(const QString& library_path) :
+ modules(library_path),
+ pose(s.all_axis_opts),
+ library_path{library_path}
+{}
+
+dylib_ptr State::current_tracker()
+{
+ const QString& module =
+#ifdef UI_FORCED_TRACKER
+ UI_FORCED_TRACKER;
+#else
+ m.tracker_dll;
+#endif
+ auto [ptr, idx] = module_by_name(module, modules.trackers());
+ return ptr;
+}
+
+dylib_ptr State::current_protocol()
+{
+ auto [ptr, idx] = module_by_name(m.protocol_dll, modules.protocols());
+ return ptr;
+}
+
+dylib_ptr State::current_filter()
+{
+ const QString& module =
+#ifdef UI_FORCED_FILTER
+ UI_FORCED_FILTER;
+#else
+ m.filter_dll;
+#endif
+ auto [ptr, idx] = module_by_name(module, modules.filters());
+ return ptr;
+}
diff --git a/logic/state.hpp b/logic/state.hpp
index 25205524..7fc06a5c 100644
--- a/logic/state.hpp
+++ b/logic/state.hpp
@@ -12,21 +12,28 @@
#include "api/plugin-support.hpp"
#include "main-settings.hpp"
#include "mappings.hpp"
-#include "extensions.hpp"
#include "work.hpp"
-#include <vector>
+#include "export.hpp"
+
+#include <memory>
#include <QString>
-struct State
+struct OTR_LOGIC_EXPORT State
{
- State(const QString& library_path) :
- modules(library_path),
- ev(modules.extensions()),
- pose(s.all_axis_opts)
- {}
+ using dylib_ptr = Modules::dylib_ptr;
+ using dylib_list = Modules::dylib_list;
+
+ explicit State(const QString& library_path);
+ static std::tuple<dylib_ptr, int> module_by_name(const QString& name, dylib_list& list);
+
+ dylib_ptr current_tracker();
+ dylib_ptr current_protocol();
+ dylib_ptr current_filter();
+
Modules modules;
- event_handler ev;
main_settings s;
+ module_settings m;
Mappings pose;
std::shared_ptr<Work> work;
+ QString library_path;
};
diff --git a/logic/tracklogger.cpp b/logic/tracklogger.cpp
index 35510cb3..758c2478 100644
--- a/logic/tracklogger.cpp
+++ b/logic/tracklogger.cpp
@@ -1,7 +1,14 @@
#include "tracklogger.hpp"
#include "pipeline.hpp"
-TrackLogger::~TrackLogger() {}
+#include <QMessageBox>
+
+TrackLogger::~TrackLogger() = default;
+
+void TrackLogger::write_pose(const double* p)
+{
+ write(p, 6);
+}
void TrackLogger::reset_dt()
{
diff --git a/logic/tracklogger.hpp b/logic/tracklogger.hpp
index 0a8d5410..84cded28 100644
--- a/logic/tracklogger.hpp
+++ b/logic/tracklogger.hpp
@@ -5,41 +5,21 @@
#include <fstream>
#include <QString>
-#include <QMessageBox>
-#include <QWidget>
+#include <QDebug>
class OTR_LOGIC_EXPORT TrackLogger
{
- TrackLogger(TrackLogger&&) = delete;
- TrackLogger(const TrackLogger&) = delete;
- TrackLogger& operator=(const TrackLogger&) = delete;
- TrackLogger& operator=(TrackLogger&&) = delete;
-
Timer t;
public:
- TrackLogger()
- {
- }
-
+ TrackLogger() = default;
virtual ~TrackLogger();
- virtual void write(const char *)
- {
- }
+ virtual void write(const char *) {}
+ virtual void write(const double *, int) {}
+ virtual void next_line() {}
- virtual void write(const double *, int)
- {
- }
-
- virtual void next_line()
- {
- }
-
- void write_pose(const double *p)
- {
- write(p, 6);
- }
+ void write_pose(const double *p);
void reset_dt();
void write_dt();
@@ -49,10 +29,10 @@ public:
class OTR_LOGIC_EXPORT TrackLoggerCSV : public TrackLogger
{
std::ofstream out;
- bool first_col;
+ bool first_col = true;
inline void handle_first_col_sep();
public:
- TrackLoggerCSV(const QString &filename) : first_col(true)
+ explicit TrackLoggerCSV(const QString &filename)
{
out.open(filename.toStdString());
}
@@ -61,5 +41,8 @@ public:
void write(const char *s) override;
void write(const double *p, int n) override;
void next_line() override;
+
+ TrackLoggerCSV(const TrackLoggerCSV&) = delete;
+ TrackLoggerCSV& operator=(const TrackLoggerCSV&) = delete;
};
diff --git a/logic/win32-shortcuts.cpp b/logic/win32-shortcuts.cpp
index 2099ce09..cb4f99b3 100644
--- a/logic/win32-shortcuts.cpp
+++ b/logic/win32-shortcuts.cpp
@@ -15,6 +15,7 @@
#include <QVariant>
#include <QDebug>
+#if 0
win_key const windows_key_mods[] {
{DIK_LCONTROL, Qt::Key_Control},
{DIK_RCONTROL, Qt::Key_Control},
@@ -25,6 +26,7 @@ win_key const windows_key_mods[] {
{DIK_LWIN, Qt::Key_Super_L},
{DIK_RWIN, Qt::Key_Super_R},
};
+#endif
static const win_key windows_key_sequences[] {
{ DIK_F1, Qt::Key_F1 },
@@ -39,6 +41,9 @@ static const win_key windows_key_sequences[] {
{ DIK_F10, Qt::Key_F10 },
{ DIK_F11, Qt::Key_F11 },
{ DIK_F12, Qt::Key_F12 },
+ { DIK_F13, Qt::Key_F13 },
+ { DIK_F14, Qt::Key_F14 },
+ { DIK_F15, Qt::Key_F15 },
{ DIK_LEFT, Qt::Key_Left },
{ DIK_RIGHT, Qt::Key_Right },
{ DIK_UP, Qt::Key_Up },
@@ -61,18 +66,6 @@ static const win_key windows_key_sequences[] {
{ DIK_MINUS, Qt::Key_Minus },
{ DIK_EQUALS, Qt::Key_Equal },
{ DIK_PERIOD, Qt::Key_Period },
- { DIK_F1, Qt::Key_F1 },
- { DIK_F2, Qt::Key_F2 },
- { DIK_F3, Qt::Key_F3 },
- { DIK_F4, Qt::Key_F4 },
- { DIK_F5, Qt::Key_F5 },
- { DIK_F6, Qt::Key_F6 },
- { DIK_F7, Qt::Key_F7 },
- { DIK_F8, Qt::Key_F8 },
- { DIK_F9, Qt::Key_F9 },
- { DIK_F10, Qt::Key_F10 },
- { DIK_F11, Qt::Key_F11 },
- { DIK_F12, Qt::Key_F12 },
{ DIK_0, Qt::Key_0 },
{ DIK_1, Qt::Key_1 },
{ DIK_2, Qt::Key_2 },
@@ -119,26 +112,26 @@ static const win_key windows_key_sequences[] {
{ DIK_PAUSE, Qt::Key_Pause},
{ DIK_NUMLOCK, Qt::Key_NumLock},
{ DIK_CAPSLOCK, Qt::Key_CapsLock},
-#define mod(x, y) static_cast<Qt::Key>(x | y)
- { DIK_NUMPAD0, mod(Qt::Key_0, Qt::KeypadModifier)},
- { DIK_NUMPAD0, mod(Qt::Key_0, Qt::KeypadModifier)},
- { DIK_NUMPAD1, mod(Qt::Key_1, Qt::KeypadModifier)},
- { DIK_NUMPAD2, mod(Qt::Key_2, Qt::KeypadModifier)},
- { DIK_NUMPAD3, mod(Qt::Key_3, Qt::KeypadModifier)},
- { DIK_NUMPAD4, mod(Qt::Key_4, Qt::KeypadModifier)},
- { DIK_NUMPAD5, mod(Qt::Key_5, Qt::KeypadModifier)},
- { DIK_NUMPAD6, mod(Qt::Key_6, Qt::KeypadModifier)},
- { DIK_NUMPAD7, mod(Qt::Key_7, Qt::KeypadModifier)},
- { DIK_NUMPAD8, mod(Qt::Key_8, Qt::KeypadModifier)},
- { DIK_NUMPAD9, mod(Qt::Key_9, Qt::KeypadModifier)},
- { DIK_NUMPADCOMMA, mod(Qt::Key_Comma, Qt::KeypadModifier)},
- { DIK_NUMPADENTER, mod(Qt::Key_Enter, Qt::KeypadModifier)},
- { DIK_NUMPADEQUALS, mod(Qt::Key_Equal, Qt::KeypadModifier)},
- { DIK_NUMPADMINUS, mod(Qt::Key_Minus, Qt::KeypadModifier)},
- { DIK_NUMPADPERIOD, mod(Qt::Key_Period, Qt::KeypadModifier)},
- { DIK_NUMPADPLUS, mod(Qt::Key_Plus, Qt::KeypadModifier)},
- { DIK_NUMPADSLASH, mod(Qt::Key_Slash, Qt::KeypadModifier)},
- { DIK_NUMPADSTAR, mod(Qt::Key_multiply, Qt::KeypadModifier)},
+#define key_mod(x, y) Qt::Key(int((x)) | int((y)))
+ { DIK_NUMPAD0, key_mod(Qt::Key_0, Qt::KeypadModifier)},
+ { DIK_NUMPAD0, key_mod(Qt::Key_0, Qt::KeypadModifier)},
+ { DIK_NUMPAD1, key_mod(Qt::Key_1, Qt::KeypadModifier)},
+ { DIK_NUMPAD2, key_mod(Qt::Key_2, Qt::KeypadModifier)},
+ { DIK_NUMPAD3, key_mod(Qt::Key_3, Qt::KeypadModifier)},
+ { DIK_NUMPAD4, key_mod(Qt::Key_4, Qt::KeypadModifier)},
+ { DIK_NUMPAD5, key_mod(Qt::Key_5, Qt::KeypadModifier)},
+ { DIK_NUMPAD6, key_mod(Qt::Key_6, Qt::KeypadModifier)},
+ { DIK_NUMPAD7, key_mod(Qt::Key_7, Qt::KeypadModifier)},
+ { DIK_NUMPAD8, key_mod(Qt::Key_8, Qt::KeypadModifier)},
+ { DIK_NUMPAD9, key_mod(Qt::Key_9, Qt::KeypadModifier)},
+ { DIK_NUMPADCOMMA, key_mod(Qt::Key_Comma, Qt::KeypadModifier)},
+ { DIK_NUMPADENTER, key_mod(Qt::Key_Enter, Qt::KeypadModifier)},
+ { DIK_NUMPADEQUALS, key_mod(Qt::Key_Equal, Qt::KeypadModifier)},
+ { DIK_NUMPADMINUS, key_mod(Qt::Key_Minus, Qt::KeypadModifier)},
+ { DIK_NUMPADPERIOD, key_mod(Qt::Key_Period, Qt::KeypadModifier)},
+ { DIK_NUMPADPLUS, key_mod(Qt::Key_Plus, Qt::KeypadModifier)},
+ { DIK_NUMPADSLASH, key_mod(Qt::Key_Slash, Qt::KeypadModifier)},
+ { DIK_NUMPADSTAR, key_mod(Qt::Key_multiply, Qt::KeypadModifier)},
};
bool win_key::to_qt(const Key& k, QKeySequence& qt_, Qt::KeyboardModifiers &mods)
@@ -158,23 +151,25 @@ bool win_key::to_qt(const Key& k, QKeySequence& qt_, Qt::KeyboardModifiers &mods
return false;
}
-bool win_key::from_qt(QKeySequence qt_, int& dik, Qt::KeyboardModifiers& mods)
+bool win_key::from_qt(const QKeySequence& qt_, int& dik, Qt::KeyboardModifiers& mods)
{
// CAVEAT don't use QVariant::toUInt() or conversion fails
- const unsigned qt = static_cast<unsigned>(QVariant(qt_).toInt());
- const unsigned our_mods = unsigned(qt & Qt::KeyboardModifierMask);
+ const unsigned qt = (unsigned)QVariant(qt_).toInt();
+ const unsigned our_mods = qt & (unsigned)Qt::KeyboardModifierMask;
+
+ if (qt == 0)
+ return false;
+ for (const win_key& wk : windows_key_sequences)
{
- for (const win_key& wk : windows_key_sequences)
+ if (unsigned(wk.qt) == qt)
{
- if (unsigned(wk.qt) == qt)
- {
- dik = wk.win;
- mods = Qt::NoModifier;
- return true;
- }
+ dik = wk.win;
+ mods = Qt::NoModifier;
+ return true;
}
}
+
{
const unsigned key = qt & ~Qt::KeyboardModifierMask;
for (const win_key& wk : windows_key_sequences)
@@ -182,7 +177,7 @@ bool win_key::from_qt(QKeySequence qt_, int& dik, Qt::KeyboardModifiers& mods)
if (unsigned(wk.qt) == key)
{
dik = wk.win;
- mods = static_cast<Qt::KeyboardModifiers>(our_mods);
+ mods = { (Qt::KeyboardModifier)our_mods };
return true;
}
}
diff --git a/logic/win32-shortcuts.h b/logic/win32-shortcuts.h
index 8cd6bdc9..afc909ed 100644
--- a/logic/win32-shortcuts.h
+++ b/logic/win32-shortcuts.h
@@ -10,11 +10,11 @@
struct OTR_LOGIC_EXPORT win_key
{
- win_key(int win, Qt::Key qt) : win(win), qt(qt) {}
+ //win_key(int win, Qt::Key qt) : win(win), qt(qt) {}
int win;
Qt::Key qt;
- static bool from_qt(QKeySequence qt_, int& dik, Qt::KeyboardModifiers &mods);
- static bool to_qt(const Key& k, QKeySequence& qt_, Qt::KeyboardModifiers &mods);
+ [[nodiscard]] static bool from_qt(const QKeySequence& qt_, int& dik, Qt::KeyboardModifiers &mods);
+ [[nodiscard]] static bool to_qt(const Key& k, QKeySequence& qt_, Qt::KeyboardModifiers &mods);
};
#endif
diff --git a/logic/work.cpp b/logic/work.cpp
index 11ec9912..8c6a3a62 100644
--- a/logic/work.cpp
+++ b/logic/work.cpp
@@ -1,6 +1,8 @@
#include "work.hpp"
#include "compat/library-path.hpp"
+#include <utility>
+
#include <QObject>
#include <QMessageBox>
#include <QFileDialog>
@@ -16,9 +18,9 @@ QString Work::browse_datalogging_file(main_settings &s)
Since the freeze is apparently random, I'm not sure it helped.
*/
QString newfilename = QFileDialog::getSaveFileName(nullptr,
- otr_tr("Select filename"),
+ tr("Select filename"),
filename,
- otr_tr("CSV File (*.csv)"),
+ tr("CSV File (*.csv)"),
nullptr);
if (!newfilename.isEmpty())
{
@@ -29,67 +31,46 @@ QString Work::browse_datalogging_file(main_settings &s)
return newfilename;
}
-std::shared_ptr<TrackLogger> Work::make_logger(main_settings &s)
+std::unique_ptr<TrackLogger> Work::make_logger(main_settings &s)
{
if (s.tracklogging_enabled)
{
QString filename = browse_datalogging_file(s);
- if (filename.isEmpty())
- {
- // The user probably canceled the file dialog. In this case we don't want to do anything.
- }
- else
+
+ if (!filename.isEmpty())
{
- auto logger = std::make_shared<TrackLoggerCSV>(s.tracklogging_filename);
+ auto logger = std::make_unique<TrackLoggerCSV>(*s.tracklogging_filename);
+
if (!logger->is_open())
{
- logger = nullptr;
QMessageBox::warning(nullptr,
- otr_tr("Logging error"),
- otr_tr("Unable to open file '%1'. Proceeding without logging.").arg(s.tracklogging_filename),
+ tr("Logging error"),
+ tr("Unable to open file '%1'. Proceeding without logging.").arg(s.tracklogging_filename),
QMessageBox::Ok, QMessageBox::NoButton);
}
else
- {
return logger;
- }
}
}
- return std::make_shared<TrackLogger>();
-}
-
-
-Work::Work(Mappings& m, event_handler& ev, QFrame* frame, std::shared_ptr<dylib> tracker_, std::shared_ptr<dylib> filter_, std::shared_ptr<dylib> proto_) :
- libs(frame, tracker_, filter_, proto_),
- logger(make_logger(s)),
- tracker(std::make_shared<pipeline>(m, libs, ev, *logger)),
- sc(std::make_shared<Shortcuts>()),
- keys {
- key_tuple(s.key_center1, [&](bool) { tracker->set_center(); }, true),
- key_tuple(s.key_center2, [&](bool) { tracker->set_center(); }, true),
- key_tuple(s.key_toggle1, [&](bool) { tracker->toggle_enabled(); }, true),
- key_tuple(s.key_toggle2, [&](bool) { tracker->toggle_enabled(); }, true),
-
- key_tuple(s.key_zero1, [&](bool) { tracker->toggle_zero(); }, true),
- key_tuple(s.key_zero2, [&](bool) { tracker->toggle_zero(); }, true),
+ return std::make_unique<TrackLogger>();
+}
- key_tuple(s.key_toggle_press1, [&](bool x) { tracker->set_enabled(!x); }, false),
- key_tuple(s.key_toggle_press2, [&](bool x) { tracker->set_enabled(!x); }, false),
- key_tuple(s.key_zero_press1, [&](bool x) { tracker->set_zero(x); }, false),
- key_tuple(s.key_zero_press2, [&](bool x) { tracker->set_zero(x); }, false),
- }
+Work::Work(const Mappings& m, QFrame* frame,
+ const dylibptr& tracker, const dylibptr& filter, const dylibptr& proto) :
+ libs(frame, tracker, filter, proto),
+ pipeline_{ m, libs, *logger }
{
if (!is_ok())
return;
reload_shortcuts();
- tracker->start();
+ pipeline_.start();
}
void Work::reload_shortcuts()
{
- sc->reload(keys);
+ sc.reload(keys);
}
bool Work::is_ok() const
@@ -97,10 +78,13 @@ bool Work::is_ok() const
return libs.correct;
}
+// TODO member dtor order looks fine, check valgrind -sh 20180706
+#if 0
Work::~Work()
{
// order matters, otherwise use-after-free -sh
- sc = nullptr;
- tracker = nullptr;
- libs = runtime_libraries();
+ //sc = nullptr;
+ //pipeline = nullptr;
+ //libs = runtime_libraries();
}
+#endif
diff --git a/logic/work.hpp b/logic/work.hpp
index 7f6cb450..ef839257 100644
--- a/logic/work.hpp
+++ b/logic/work.hpp
@@ -16,6 +16,7 @@
#include "tracklogger.hpp"
#include "logic/runtime-libraries.hpp"
#include "api/plugin-support.hpp"
+#include "compat/tr.hpp"
#include <QObject>
#include <QFrame>
@@ -24,23 +25,42 @@
#include <tuple>
#include <functional>
-struct OTR_LOGIC_EXPORT Work
+class OTR_LOGIC_EXPORT Work final : public QObject
{
+ Q_OBJECT
+
+ using dylibptr = std::shared_ptr<dylib>;
+
+ static std::unique_ptr<TrackLogger> make_logger(main_settings &s);
+ static QString browse_datalogging_file(main_settings &s);
+
+public:
using fn_t = std::function<void(bool)>;
using key_tuple = std::tuple<key_opts&, fn_t, bool>;
- main_settings s; // tracker needs settings, so settings must come before it
+ main_settings s; // pipeline needs settings, so settings must come before it
runtime_libraries libs; // idem
- std::shared_ptr<TrackLogger> logger; // must come before tracker, since tracker depends on it
- std::shared_ptr<pipeline> tracker;
- std::shared_ptr<Shortcuts> sc;
- std::vector<key_tuple> keys;
+ std::unique_ptr<TrackLogger> logger { make_logger(s) }; // must come before pipeline, since pipeline depends on it
+ pipeline pipeline_;
+ Shortcuts sc;
+
+ std::vector<key_tuple> keys {
+ // third argument means "keydown only"
+ key_tuple(s.key_center1, [this](bool x) { pipeline_.set_held_center(x); }, false),
+ key_tuple(s.key_center2, [this](bool x) { pipeline_.set_held_center(x); }, false),
- Work(Mappings& m, event_handler& ev, QFrame* frame, std::shared_ptr<dylib> tracker, std::shared_ptr<dylib> filter, std::shared_ptr<dylib> proto);
- ~Work();
+ key_tuple(s.key_toggle1, [this](bool) { pipeline_.toggle_enabled(); }, true),
+ key_tuple(s.key_toggle2, [this](bool) { pipeline_.toggle_enabled(); }, true),
+ key_tuple(s.key_toggle_press1, [this](bool x) { pipeline_.set_enabled(!x); }, false),
+ key_tuple(s.key_toggle_press2, [this](bool x) { pipeline_.set_enabled(!x); }, false),
+
+ key_tuple(s.key_zero1, [this](bool) { pipeline_.toggle_zero(); }, true),
+ key_tuple(s.key_zero2, [this](bool) { pipeline_.toggle_zero(); }, true),
+ key_tuple(s.key_zero_press1, [this](bool x) { pipeline_.set_zero(x); }, false),
+ key_tuple(s.key_zero_press2, [this](bool x) { pipeline_.set_zero(x); }, false),
+ };
+
+ Work(const Mappings& m, QFrame* frame,
+ const dylibptr& tracker, const dylibptr& filter, const dylibptr& proto);
void reload_shortcuts();
bool is_ok() const;
-
-private:
- static std::shared_ptr<TrackLogger> make_logger(main_settings &s);
- static QString browse_datalogging_file(main_settings &s);
};
diff --git a/macosx/CMakeLists.txt b/macosx/CMakeLists.txt
index 89901251..8520e9e9 100644
--- a/macosx/CMakeLists.txt
+++ b/macosx/CMakeLists.txt
@@ -1,13 +1,11 @@
if(APPLE)
otr_escape_string(srcdir "${CMAKE_SOURCE_DIR}")
- otr_escape_string(bindir "${CMAKE_BINARY_DIR}")
otr_escape_string(instdir "${CMAKE_INSTALL_PREFIX}")
otr_escape_string(commit "${OPENTRACK_COMMIT}")
install(CODE "
- execute_process(COMMAND /bin/sh \"${srcdir}/macosx/make-app-bundle.sh\"
+ execute_process(COMMAND /bin/sh \"${srcdir}/macosx/make-app-bundle.sh\"
\"${srcdir}/macosx\"
\"${instdir}\"
- \"${bindir}\"
- \"${commit}\")
+ \"${commit}\")
")
endif()
diff --git a/macosx/opentrack.app/Contents/Info.plist b/macosx/Info.plist
index 41ca402a..3fd8b614 100644
--- a/macosx/opentrack.app/Contents/Info.plist
+++ b/macosx/Info.plist
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
- <string>opentrack.sh</string>
+ <string>opentrack</string>
<key>CFBundleIdentifier</key>
<string>com.github.opentrack</string>
<key>CFBundleName</key>
@@ -22,5 +22,13 @@
<string>opentrack</string>
<key>CFBundleSignature</key>
<string>????</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>NSHighResolutionCapable</key>
+ <string>True</string>
+ <key>NSCameraUsageDescription</key>
+ <string>OpenTrack need's to access your camera to capture track head position.</string>
+ <key>NSDocumentsFolderUsageDescription</key>
+ <string>OpenTrack need's to access your to your documents folder to store settings</string>
</dict>
</plist>
diff --git a/macosx/PkgInfo b/macosx/PkgInfo
new file mode 100644
index 00000000..6f749b0f
--- /dev/null
+++ b/macosx/PkgInfo
@@ -0,0 +1 @@
+APPL????
diff --git a/macosx/dmgbackground.png b/macosx/dmgbackground.png
new file mode 100644
index 00000000..e3a398c0
--- /dev/null
+++ b/macosx/dmgbackground.png
Binary files differ
diff --git a/macosx/install-fail-tool b/macosx/install-fail-tool
index a5464c18..16284562 100755
--- a/macosx/install-fail-tool
+++ b/macosx/install-fail-tool
@@ -4,22 +4,22 @@ test -n "$1" || exit 1
dir="$1"
-for i in "$dir"/* "$dir"/*/* "$dir"/*/*/*; do
- { test -x "$i" && test -f "$i"; } || continue
+for i in "$dir"/*; do
+ echo $i
+ { test -f "$i"; } || continue
case "$i" in
*.dll|*.exe) continue ;;
*) : ;;
esac
- case "$i" in
- *.dylib|*.bin) strip -x "$i" ;; esac
echo ---- $i ----
install_name_tool -id "@executable_path/$(echo "$i" | sed -e "s,^$dir/,,")" "$i"
otool -L "$i" | awk '{ print $1 }' |
while read l; do
j="$(basename -- "$l")"
+ echo === $j ===
if test -e "$dir/$j"; then
- install_name_tool -change "$l" "@executable_path/$j" "$i"
+ install_name_tool -change "$l" "@rpath/$j" "$i"
fi
done
done
diff --git a/macosx/make-app-bundle.sh b/macosx/make-app-bundle.sh
index 05259702..d04dc1bd 100644..100755
--- a/macosx/make-app-bundle.sh
+++ b/macosx/make-app-bundle.sh
@@ -1,41 +1,89 @@
#!/bin/sh
+# exit when any command fails
+set -e
+
+# keep track of the last executed command
+trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG
+# echo an error message before exiting
+trap 'echo "--\n--\n--\n--\n\"${last_command}\" command failed with exit code $?."' EXIT
+
APPNAME=opentrack
+# Alternative we could look at https://github.com/arl/macdeployqtfix ??
-dir="$1"
-test -n "$dir" || exit 1
+#macosx directory
+dir="$1"
+test -n "$dir"
+# install directory
install="$2"
-test -n "$install" || exit 1
-output_dir="$3"
-test -n "$output_dir" || exit 1
-version="$4"
-test -n "$version" || exit 1
+test -n "$install"
+version="$3"
+test -n "$version"
tmp="$(mktemp -d "/tmp/$APPNAME-tmp.XXXXXXX")"
-test $? -eq 0 || exit 1
+test $? -eq 0
+
-rm -f -- "$install/.DS_Store"
-sh "$dir/install-fail-tool" "$install"
+# Add rpath for application so it can find the libraries
+#install_name_tool -add_rpath @executable_path/../Frameworks "$install/$APPNAME.app/Contents/MacOS/$APPNAME"
-cp -R "$dir/opentrack.app" "$tmp/" || exit 1
-cp -R "$install" "$tmp/$APPNAME.app/Contents/MacOS" || exit 1
-sed -i '' -e "s#@OPENTRACK-VERSION@#$version#g" "$tmp/$APPNAME.app/Contents/Info.plist" || exit 1
+# Copy our own plist and set correct version
+cp "$dir/Info.plist" "$install/$APPNAME.app/Contents/"
+sed -i '' -e "s#@OPENTRACK-VERSION@#$version#g" "$install/$APPNAME.app/Contents/Info.plist"
-mkdir "$tmp/$APPNAME.iconset" || exit 1
-mkdir "$tmp/$APPNAME.app/Contents/Resources" || exit 1
-cp "$dir"/opentrack.sh "$tmp/$APPNAME.app/Contents/MacOS" || exit 1
+# Copy PkgInfo
+cp "$dir/PkgInfo" "$install/$APPNAME.app/Contents/"
-sips -z 16 16 "$dir/../gui/images/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_16x16.png" || exit 1
-sips -z 32 32 "$dir/../gui/images/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_16x16@2x.png" || exit 1
-sips -z 32 32 "$dir/../gui/images/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_32x32.png" || exit 1
-sips -z 64 64 "$dir/../gui/images/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_32x32@2x.png" || exit 1
-sips -z 128 128 "$dir/../gui/images/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_128x128.png" || exit 1
+# Copy plugins
+mkdir -p "$install/$APPNAME.app/Contents/MacOS/Plugins"
+cp -r "$install/Plugins" "$install/$APPNAME.app/Contents/MacOS/"
-iconutil -c icns -o "$tmp/$APPNAME.app/Contents/Resources/$APPNAME.icns" "$tmp/$APPNAME.iconset"
-rm -r "$tmp/$APPNAME.iconset"
+# Use either of these, two of them at the same time will break things!
+macdeployqt "$install/$APPNAME.app" -libpath="$install/Library"
+#sh "$dir/install-fail-tool" "$install/$APPNAME.app/Contents/Frameworks"
-cd "$tmp" || exit 1
-rm -f "$output_dir/$version-macosx.zip"
-zip -9r "$output_dir/$version-macosx.zip" "$APPNAME.app" || exit 1
+# Create an 512 resolution size for the icon (for retina displays mostly)
+#gm convert -size 512x512 "$dir/../gui/images/opentrack.png" "$tmp/opentrack.png"
+convert "$dir/../gui/images/opentrack.png" -filter triangle -resize 512x512 "$tmp/opentrack.png"
+
+# Build iconset
+mkdir "$tmp/$APPNAME.iconset"
+sips -z 16 16 "$tmp/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_16x16.png"
+sips -z 32 32 "$tmp/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_16x16@2x.png"
+sips -z 32 32 "$tmp/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_32x32.png"
+sips -z 64 64 "$tmp/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_32x32@2x.png"
+sips -z 128 128 "$tmp/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_128x128.png"
+sips -z 256 256 "$tmp/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_128x128@2x.png"
+sips -z 512 512 "$tmp/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_256x256@2x.png"
+sips -z 512 512 "$tmp/opentrack.png" --out "$tmp/$APPNAME.iconset/icon_512x512.png"
+iconutil -c icns -o "$install/$APPNAME.app/Contents/Resources/$APPNAME.icns" "$tmp/$APPNAME.iconset"
rm -rf "$tmp"
-ls -lh "$output_dir/$version-macosx.zip"
+
+#Build DMG
+#https://github.com/andreyvit/create-dmg
+rm -rf $install/../*.dmg
+create-dmg \
+ --volname "$APPNAME" \
+ --volicon "$install/$APPNAME.app/Contents/Resources/$APPNAME.icns" \
+ --window-pos 200 120 \
+ --window-size 800 450 \
+ --icon-size 80 \
+ --background "$dir/dmgbackground.png" \
+ --icon "$APPNAME.app" 200 180 \
+ --app-drop-link 420 180 \
+ --hide-extension "$APPNAME.app" \
+ --no-internet-enable \
+ --add-folder "Document" "$install/doc" 20 40 \
+ --add-folder "Xplane-Plugin" "$install/xplane" 420 40 \
+ --add-folder "thirdparty" "$install/thirdparty" 620 40 \
+ "$version.dmg" \
+ "$install/$APPNAME.app"
+
+# Check if we have a DMG otherwise fail
+FILE=$install/../$version.dmg
+if [ -f $FILE ]; then
+ ls -ial $install/../*.dmg
+else
+ echo "Failed to create ${FILE}"
+ exit 2
+fi
diff --git a/macosx/opentrack.app/Contents/PkgInfo b/macosx/opentrack.app/Contents/PkgInfo
deleted file mode 100644
index b18f8c6c..00000000
--- a/macosx/opentrack.app/Contents/PkgInfo
+++ /dev/null
@@ -1 +0,0 @@
-APPLopentrack
diff --git a/macosx/opentrack.sh b/macosx/opentrack.sh
deleted file mode 100755
index 74a911e2..00000000
--- a/macosx/opentrack.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-
-cd -- "$(dirname -- "$0")" &&
-exec ./opentrack.bin -platformpluginpath "$(pwd)" "$@"
-
-exit 1
diff --git a/migration/20160906_00-mappings.cpp b/migration/20160906_00-mappings.cpp
index 567fbdcb..2bc00084 100644
--- a/migration/20160906_00-mappings.cpp
+++ b/migration/20160906_00-mappings.cpp
@@ -18,6 +18,8 @@
#include <QDebug>
+using namespace options;
+using namespace options::globals;
using namespace migrations;
static const char* const old_names[] =
@@ -43,22 +45,22 @@ static QList<QList<QPointF>> get_old_splines()
{
QList<QList<QPointF>> ret;
- return group::with_settings_object([&](QSettings& settings) {
+ return with_settings_object([&](QSettings& settings) {
for (const char* name : old_names)
{
+ const int max = settings.value("point-count", 0).toInt();
+
+ if (max < 0 || max > 1 << 16)
+ return ret;
+
QList<QPointF> points;
+ points.reserve(max);
settings.beginGroup(QString("Curves-%1").arg(name));
- const int max = settings.value("point-count", 0).toInt();
-
for (int i = 0; i < max; i++)
- {
- QPointF new_point(settings.value(QString("point-%1-x").arg(i), 0).toDouble(),
- settings.value(QString("point-%1-y").arg(i), 0).toDouble());
-
- points.append(new_point);
- }
+ points.append({ settings.value(QString("point-%1-x").arg(i), 0).toDouble(),
+ settings.value(QString("point-%1-y").arg(i), 0).toDouble() });
settings.endGroup();
@@ -85,7 +87,7 @@ struct mappings_from_2_3_0_rc11 : migration
// run only if old splines exist
for (const QList<QPointF>& points : get_old_splines())
- if (points.size())
+ if (!points.empty())
return true;
}
@@ -95,7 +97,7 @@ struct mappings_from_2_3_0_rc11 : migration
void run() override
{
- group::with_settings_object([this](QSettings&) {
+ with_settings_object([](QSettings&) {
const QList<QList<QPointF>> old_mappings = get_old_splines();
for (int i = 0; i < 12; i++)
@@ -111,4 +113,4 @@ struct mappings_from_2_3_0_rc11 : migration
}
};
-OPENTRACK_MIGRATION(mappings_from_2_3_0_rc11);
+OPENTRACK_MIGRATION(mappings_from_2_3_0_rc11)
diff --git a/migration/20160906_01-axis-signs.cpp b/migration/20160906_01-axis-signs.cpp
index 7b0be70a..eeb6963d 100644
--- a/migration/20160906_01-axis-signs.cpp
+++ b/migration/20160906_01-axis-signs.cpp
@@ -41,7 +41,7 @@ struct axis_signs_split_rc11 : migration
void run() override;
};
-OPENTRACK_MIGRATION(axis_signs_split_rc11);
+OPENTRACK_MIGRATION(axis_signs_split_rc11)
bool axis_signs_split_rc11::should_run() const
{
@@ -73,7 +73,7 @@ void axis_signs_split_rc11::run()
for (const char* name : axis_names)
new_bundle->store_kv(alt_sign_fmt.arg(name),
- QVariant(old_bundle->get<bool>(alt_sign_fmt.arg(name))));
+ old_bundle->get_variant(alt_sign_fmt.arg(name)));
new_bundle->save();
}
diff --git a/migration/20160906_02-modules.cpp b/migration/20160906_02-modules.cpp
index 9ce2b9dc..46745112 100644
--- a/migration/20160906_02-modules.cpp
+++ b/migration/20160906_02-modules.cpp
@@ -58,10 +58,10 @@ struct split_modules_rc11 : migration
bundle old_bundle = make_bundle("opentrack-ui");
for (const char* name : module_names)
- new_bundle->store_kv(name, QVariant(old_bundle->get<QString>(name)));
+ new_bundle->store_kv(name, old_bundle->get_variant(name));
new_bundle->save();
}
};
-OPENTRACK_MIGRATION(split_modules_rc11);
+OPENTRACK_MIGRATION(split_modules_rc11)
diff --git a/migration/20170420_00-udp-naming.cpp b/migration/20170420_00-udp-naming.cpp
index 0361b7b9..d8117526 100644
--- a/migration/20170420_00-udp-naming.cpp
+++ b/migration/20170420_00-udp-naming.cpp
@@ -51,4 +51,4 @@ struct rename_udp_stuff : migration
}
};
-OPENTRACK_MIGRATION(rename_udp_stuff);
+OPENTRACK_MIGRATION(rename_udp_stuff)
diff --git a/migration/20171013_00-tracker-pt-threshold.cpp b/migration/20171013_00-tracker-pt-threshold.cpp
index aab64de7..23f10659 100644
--- a/migration/20171013_00-tracker-pt-threshold.cpp
+++ b/migration/20171013_00-tracker-pt-threshold.cpp
@@ -24,7 +24,6 @@ struct move_int_to_slider : migration
return "20171013_00";
}
-
QString name() const override
{
return "tracker/pt threshold slider (int -> slider_value)";
@@ -32,22 +31,22 @@ struct move_int_to_slider : migration
bool should_run() const override
{
- bundle b = make_bundle("tracker-pt");
+ bundle b = make_bundle(bundle_name);
return b->contains(old_name) && !b->contains(new_name);
}
void run() override
{
- bundle b = make_bundle("tracker-pt");
+ bundle b = make_bundle(bundle_name);
value<int> old_val(b, old_name, 128);
- value<slider_value> new_val(b, new_name, slider_value(128, 0, 255));
+ value<slider_value> new_val(b, new_name, { 128, 0, 255 });
- new_val = slider_value(old_val.to<int>(), 0, 255);
+ new_val = { *old_val, 0, 255 };
b->save();
}
};
-OPENTRACK_MIGRATION(move_int_to_slider);
+OPENTRACK_MIGRATION(move_int_to_slider)
diff --git a/migration/20171020_00-max-pitch-output.cpp b/migration/20171020_00-max-pitch-output.cpp
index 0699ac48..868bb5b8 100644
--- a/migration/20171020_00-max-pitch-output.cpp
+++ b/migration/20171020_00-max-pitch-output.cpp
@@ -49,7 +49,7 @@ struct max_pitch_output : migration
spline& pitch_spline_2 = pitch_map.spline_alt;
for (const spline& spl : { pitch_spline_1, pitch_spline_2 })
- for (QPointF& point : spl.get_points())
+ for (const QPointF& point : spl.get_points())
if (point.y() - 1e-2 > 90)
return true;
@@ -67,4 +67,4 @@ struct max_pitch_output : migration
}
};
-OPENTRACK_MIGRATION(max_pitch_output);
+OPENTRACK_MIGRATION(max_pitch_output)
diff --git a/migration/20180102_00-process-detector-separator.cpp b/migration/20180102_00-process-detector-separator.cpp
index ddb01cb4..3656bf73 100644
--- a/migration/20180102_00-process-detector-separator.cpp
+++ b/migration/20180102_00-process-detector-separator.cpp
@@ -2,6 +2,7 @@
#include "options/options.hpp"
using namespace options;
+using namespace options::globals;
using namespace migrations;
static constexpr auto OLD_RECORD_SEPARATOR = QChar('|');
@@ -26,7 +27,7 @@ struct process_detector_record_separator : migration
bool should_run() const override
{
- return group::with_global_settings_object([](const QSettings& s)
+ return with_global_settings_object([](const QSettings& s)
{
const QString old_value = s.value(KEY_NAME).toString();
return old_value.contains(OLD_RECORD_SEPARATOR);
@@ -35,14 +36,15 @@ struct process_detector_record_separator : migration
void run() override
{
- return group::with_global_settings_object([](QSettings& s)
+ return with_global_settings_object([](QSettings& s)
{
QString value = s.value(KEY_NAME).toString();
value.replace(OLD_UNIT_SEPARATOR, NEW_UNIT_SEPARATOR);
value.replace(OLD_RECORD_SEPARATOR, NEW_RECORD_SEPARATOR);
s.setValue(KEY_NAME, value);
+ mark_global_ini_modified();
});
}
};
-OPENTRACK_MIGRATION(process_detector_record_separator);
+OPENTRACK_MIGRATION(process_detector_record_separator)
diff --git a/migration/20180118_00-reltrans.cpp b/migration/20180118_00-reltrans.cpp
index ba35e7f0..09e3847e 100644
--- a/migration/20180118_00-reltrans.cpp
+++ b/migration/20180118_00-reltrans.cpp
@@ -35,10 +35,10 @@ struct reltrans_enum : migration
void run() override
{
auto b = make_bundle("opentrack-ui");
- bool value = b->get<bool>(old_name);
+ bool value = b->get_variant(old_name).value<bool>();
b->store_kv(new_name, int(value ? reltrans_enabled : reltrans_disabled));
b->save();
}
};
-OPENTRACK_MIGRATION(reltrans_enum);
+OPENTRACK_MIGRATION(reltrans_enum)
diff --git a/migration/20180428_00-module-names.cpp b/migration/20180428_00-module-names.cpp
new file mode 100644
index 00000000..9558829f
--- /dev/null
+++ b/migration/20180428_00-module-names.cpp
@@ -0,0 +1,92 @@
+#include "migration.hpp"
+#include "options/options.hpp"
+
+using namespace migrations;
+using namespace options;
+
+#include "api/plugin-support.hpp"
+#include "compat/library-path.hpp"
+
+struct module_names : migration
+{
+ using dylib_ptr = Modules::dylib_ptr;
+ using dylib_list = Modules::dylib_list;
+
+ struct module_type {
+ QString name, def;
+ dylib_list const& list;
+ };
+
+ Modules m { OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH, dylib_load_quiet };
+
+ module_type types[3] {
+ { "tracker-dll", "pt", m.trackers() },
+ { "protocol-dll", "freetrack", m.protocols() },
+ { "filter-dll", "accela", m.filters() },
+ };
+
+ bundle b { make_bundle("modules") };
+
+ QString unique_date() const override
+ {
+ return "20180428_00";
+ }
+
+ QString name() const override
+ {
+ return "module names";
+ }
+
+ bool should_run() const override
+ {
+ for (const module_type& type : types)
+ {
+ if (!b->contains(type.name))
+ continue;
+
+ const QString value = b->get_variant(type.name).value<QString>();
+
+ for (const dylib_ptr& lib : type.list)
+ {
+ if (value == lib->name && value != lib->module_name)
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void run() override
+ {
+ for (module_type& type : types)
+ {
+ if (!b->contains(type.name))
+ continue;
+
+ const QString value = b->get_variant(type.name).value<QString>();
+
+ bool found = false;
+
+ for (const dylib_ptr& lib : type.list)
+ {
+ if (value == lib->name && value != lib->module_name)
+ {
+ qDebug() << type.name << value << "=>" << lib->module_name;
+ b->store_kv(type.name, lib->module_name);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ qDebug() << type.name << value << "not found";
+ b->store_kv(type.name, QVariant());
+ }
+ }
+
+ b->save();
+ }
+};
+
+OPENTRACK_MIGRATION(module_names)
diff --git a/migration/20220105_00-pt-grayscale.cpp b/migration/20220105_00-pt-grayscale.cpp
new file mode 100644
index 00000000..44bdc470
--- /dev/null
+++ b/migration/20220105_00-pt-grayscale.cpp
@@ -0,0 +1,38 @@
+#include "migration.hpp"
+#include "options/options.hpp"
+
+using namespace migrations;
+using namespace options;
+
+#include "api/plugin-support.hpp"
+#include "compat/library-path.hpp"
+
+struct pt_color_grayscale : migration
+{
+ bundle b { make_bundle("tracker-pt") };
+ enum : int { pt_color_average = 5, pt_color_bt709 = 2, };
+
+ QString unique_date() const override
+ {
+ return "20220105_00";
+ }
+
+ QString name() const override
+ {
+ return "pt color enum";
+ }
+
+ bool should_run() const override
+ {
+ auto x = b->get_variant("blob-color").toInt();
+ return x == pt_color_average;
+ }
+
+ void run() override
+ {
+ b->store_kv("blob-color", QVariant::fromValue((int)pt_color_bt709));
+ b->save();
+ }
+};
+
+OPENTRACK_MIGRATION(pt_color_grayscale)
diff --git a/migration/20220126_00-camera-name.cpp b/migration/20220126_00-camera-name.cpp
new file mode 100644
index 00000000..adb6d718
--- /dev/null
+++ b/migration/20220126_00-camera-name.cpp
@@ -0,0 +1,79 @@
+#ifdef _WIN32
+#include "migration.hpp"
+#include "options/options.hpp"
+#include "compat/camera-names.hpp"
+
+using namespace migrations;
+using namespace options;
+
+#include "api/plugin-support.hpp"
+#include "compat/library-path.hpp"
+#include <tuple>
+#include <QString>
+
+static const std::tuple<const char*, const char*> modules[] = {
+ { "tracker-aruco", "camera-name" },
+ { "tracker-easy", "camera-name" },
+ { "neuralnet-tracker", "camera-name" },
+ { "tracker-pt", "camera-name" },
+};
+
+struct win32_camera_name : migration
+{
+ QString unique_date() const override
+ {
+ return "20220126_00";
+ }
+
+ QString name() const override
+ {
+ return "camera name";
+ }
+
+ bool should_run() const override
+ {
+ for (const auto& [name, prop] : modules)
+ {
+ bundle b { make_bundle(name) };
+ QString str = b->get_variant(prop).toString();
+ if (!str.isEmpty() && !str.contains(" ["))
+ return true;
+ }
+ return false;
+ }
+
+ void run() override
+ {
+ auto cameras = get_camera_names();
+
+ for (const auto& [bundle_name, prop] : modules)
+ {
+ bundle b { make_bundle(bundle_name) };
+ QString name = b->get_variant(prop).toString();
+ if (name.isEmpty() || name.contains(" ["))
+ continue;
+ auto full_name_of = [&](const QString& str) {
+ QString ret = str;
+ auto prefix = str + " [";
+ for (const auto& [x, _] : cameras)
+ {
+ if (x == str)
+ return str;
+ if (x.startsWith(prefix))
+ ret = x;
+ }
+ return ret;
+ };
+ auto full_name = full_name_of(name);
+ if (name != full_name)
+ {
+ b->store_kv(prop, full_name);
+ b->save();
+ }
+ }
+ }
+};
+
+OPENTRACK_MIGRATION(win32_camera_name)
+
+#endif
diff --git a/migration/CMakeLists.txt b/migration/CMakeLists.txt
index 9a7f7dc9..effeddcb 100644
--- a/migration/CMakeLists.txt
+++ b/migration/CMakeLists.txt
@@ -1,2 +1,5 @@
otr_module(migration BIN)
-target_link_libraries(opentrack-migration opentrack-logic opentrack-spline)
+target_link_libraries(${self} opentrack-logic opentrack-spline)
+if(CMAKE_COMPILER_IS_CLANGXX)
+ target_compile_options(${self} PRIVATE -Wno-weak-vtables)
+endif()
diff --git a/migration/lang/de_DE.ts b/migration/lang/de_DE.ts
new file mode 100644
index 00000000..1552582e
--- /dev/null
+++ b/migration/lang/de_DE.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+</TS>
diff --git a/migration/lang/zh_CN.ts b/migration/lang/zh_CN.ts
index 6401616d..e5ca8aa9 100644
--- a/migration/lang/zh_CN.ts
+++ b/migration/lang/zh_CN.ts
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
</TS>
diff --git a/migration/migration.cpp b/migration/migration.cpp
index b92d6384..f4b1739b 100644
--- a/migration/migration.cpp
+++ b/migration/migration.cpp
@@ -20,17 +20,31 @@
#include <memory>
+using namespace options::globals;
+
// individual migrations are run in the UI thread. they can be interactive if necessary.
-namespace migrations {
+namespace migrations::detail {
+
-namespace detail {
+std::vector<mptr>& migrator::migration_list()
+{
+ static std::vector<mptr> v;
+ return v;
+}
-void migrator::register_migration(migration* m)
+std::vector<mfun>& migrator::migration_thunks()
+{
+ static std::vector<mfun> v;
+ return v;
+}
+
+
+void migrator::register_migration(mptr const& m)
{
const QString date = m->unique_date();
- for (migration* m2 : migrations())
+ for (mptr const& m2 : migration_list())
if (m2->unique_date() == date)
std::abort();
@@ -47,15 +61,13 @@ void migrator::register_migration(migration* m)
bool ok = true;
- if (year < "2016")
- abort();
-
- const int month_ = to_int(month, ok), day_ = to_int(day, ok);
+ const int year_ = to_int(year, ok),
+ month_ = to_int(month, ok),
+ day_ = to_int(day, ok);
- (void) to_int(year, ok);
- (void) to_int(serial, ok);
+ (void)to_int(serial, ok);
- if (!ok)
+ if (!ok || year_ < 1970)
abort();
if (month_ < 1 || month_ > 12)
@@ -64,20 +76,45 @@ void migrator::register_migration(migration* m)
if (day_ < 1 || day_ > 31)
abort();
- migrations().push_back(m);
+ migration_list().push_back(m);
}
-std::vector<migration*>& migrator::migrations()
+void migrator::eval_thunks()
{
- static std::vector<migration*> ret;
- return ret;
+ for (auto& fun : migration_thunks())
+ {
+ mptr m = fun();
+ register_migration(m);
+ }
+ if (!migration_thunks().empty())
+ sort_migrations();
+ migration_thunks().clear();
+}
+
+void migrator::add_migration_thunk(mfun& thunk)
+{
+ migration_thunks().push_back(thunk);
+}
+
+std::vector<mptr>& migrator::migrations()
+{
+ eval_thunks();
+ return migration_list();
+}
+
+void migrator::sort_migrations()
+{
+ std::sort(migration_list().begin(), migration_list().end(),
+ [](const mptr x, const mptr y) {
+ return x->unique_date() < y->unique_date();
+ });
}
QString migrator::last_migration_time()
{
QString ret;
- options::group::with_settings_object([&](QSettings& s) {
+ with_settings_object([&](QSettings& s) {
s.beginGroup("migrations");
ret = s.value("last-migration-at", "19700101_00").toString();
s.endGroup();
@@ -88,9 +125,9 @@ QString migrator::last_migration_time()
QString migrator::time_after_migrations()
{
- const std::vector<migration*> list = sorted_migrations();
+ const std::vector<mptr>& list = migrations();
- if (list.size() == 0u)
+ if (list.empty())
return QStringLiteral("19700101_00");
QString ret = list[list.size() - 1]->unique_date();
@@ -101,28 +138,18 @@ QString migrator::time_after_migrations()
void migrator::set_last_migration_time(const QString& val)
{
- options::group::with_settings_object([&](QSettings& s) {
+ with_settings_object([&](QSettings& s) {
s.beginGroup("migrations");
const QString old_value = s.value("last-migration-at", "").toString();
if (val != old_value)
{
s.setValue("last-migration-at", val);
- options::group::mark_ini_modified();
+ options::globals::detail::mark_ini_modified();
}
s.endGroup();
});
}
-std::vector<migration*> migrator::sorted_migrations()
-{
- std::vector<migration*> list(migrations());
-
- using mm = migration*;
-
- std::sort(list.begin(), list.end(), [](const mm x, const mm y) { return x->unique_date() < y->unique_date(); });
- return list;
-}
-
int migrator::to_int(const QString& str, bool& ok)
{
bool tmp = false;
@@ -133,61 +160,55 @@ int migrator::to_int(const QString& str, bool& ok)
std::vector<QString> migrator::run()
{
- std::vector<migration*> migrations = sorted_migrations();
std::vector<QString> done;
const QString last_migration = last_migration_time();
- for (migration* m_ : migrations)
- {
- migration& m(*m_);
-
- const QString date = m.unique_date();
-
- if (date <= last_migration)
- continue;
-
- if (m.should_run())
+ with_settings_object([&](QSettings&) {
+ for (mptr const& m : migrations())
{
- m.run();
- done.push_back(m.name());
+ const QString date = m->unique_date();
+
+ if (date <= last_migration)
+ continue;
+
+ if (m->should_run())
+ {
+ const QByteArray name = m->name().toUtf8();
+ const QByteArray date = m->unique_date().toUtf8();
+ qDebug() << "migrate:" << date.constData() << name.constData();
+ m->run();
+ done.push_back(m->name());
+ }
}
- }
-
- mark_config_as_not_needing_migration();
-
- if (done.size())
- {
- for (const QString& name : done)
- {
- const QByteArray data = name.toUtf8();
- qDebug() << "migrate:" << data.constData();
- }
- }
+ mark_profile_as_not_needing_migration();
+ });
return done;
}
-}
+} // ns migrations::detail
+
+namespace migrations {
-migration::migration() {}
-migration::~migration() {}
+migration::migration() = default;
+migration::~migration() = default;
-} // ns
+} // ns migrations
std::vector<QString> run_migrations()
{
return migrations::detail::migrator::run();
}
-void mark_config_as_not_needing_migration()
+void mark_profile_as_not_needing_migration()
{
using m = migrations::detail::migrator;
- m::mark_config_as_not_needing_migration();
+ m::mark_profile_as_not_needing_migration();
}
-void migrations::detail::migrator::mark_config_as_not_needing_migration()
+void migrations::detail::migrator::mark_profile_as_not_needing_migration()
{
set_last_migration_time(time_after_migrations());
}
diff --git a/migration/migration.hpp b/migration/migration.hpp
index 5f99de7a..7fc18c97 100644
--- a/migration/migration.hpp
+++ b/migration/migration.hpp
@@ -9,29 +9,43 @@
#pragma once
#include <QString>
-#include <vector>
-
#include "export.hpp"
+#include <memory>
+#include <vector>
+#include <functional>
+
namespace migrations {
-class migration;
+struct migration;
class registrator;
namespace detail {
- class migrator final
+ using mptr = std::shared_ptr<migration>;
+ using mfun = std::function<mptr ()>;
+
+ struct migrator
{
- static std::vector<migration*>& migrations();
+ static std::vector<QString> run();
+ static void add_migration_thunk(std::function<mptr()>& thunk);
+ static void mark_profile_as_not_needing_migration();
+
+ private:
+ static void sort_migrations();
+
+ static void register_migration(const mptr& m);
+ static std::vector<mptr>& migrations();
+
+ static void eval_thunks();
+
static QString last_migration_time();
static QString time_after_migrations();
+
static void set_last_migration_time(const QString& val);
- migrator() = delete;
- static std::vector<migration*> sorted_migrations();
static int to_int(const QString& str, bool& ok);
- public:
- static std::vector<QString> run();
- static void register_migration(migration* m);
- static void mark_config_as_not_needing_migration();
+
+ static std::vector<mptr>& migration_list();
+ static std::vector<mfun>& migration_thunks();
};
template<typename t>
@@ -39,31 +53,28 @@ namespace detail {
{
registrator()
{
- static t m;
- migrator::register_migration(static_cast<migration*>(&m));
+ mfun f { [] { return std::shared_ptr<migration>(new t); } };
+
+ migrator::add_migration_thunk(f);
}
};
}
-#ifndef __COUNTER__
-# error "oops, need __COUNTER__ extension for preprocessor"
-#endif
+#define OPENTRACK_MIGRATION3(type, ctr) \
+ static const char init_##ctr = (::migrations::detail::registrator<type>{}, 0);
-#define OPENTRACK_MIGRATION(type) static ::migrations::detail::registrator<type> opentrack_migration_registrator__ ## __COUNTER__ ## _gensym
+#define OPENTRACK_MIGRATION2(type, ctr) \
+ OPENTRACK_MIGRATION3(type, ctr)
-#ifdef Q_CREATOR_RUN
-# pragma clang diagnostic ignored "-Wweak-vtables"
-#endif
+#define OPENTRACK_MIGRATION(type) \
+ OPENTRACK_MIGRATION2(type, __COUNTER__)
-class migration
+struct migration
{
- migration& operator=(const migration&) = delete;
+ migration();
migration(const migration&) = delete;
- migration& operator=(migration&&) = delete;
- migration(migration&&) = delete;
+ migration& operator=(const migration&) = delete;
-public:
- migration();
virtual ~migration();
virtual QString unique_date() const = 0;
virtual QString name() const = 0;
@@ -71,7 +82,7 @@ public:
virtual void run() = 0;
};
-}
+} // ns migrations
OTR_MIGRATION_EXPORT std::vector<QString> run_migrations();
-OTR_MIGRATION_EXPORT void mark_config_as_not_needing_migration();
+OTR_MIGRATION_EXPORT void mark_profile_as_not_needing_migration();
diff --git a/opentrack/CMakeLists.txt b/opentrack/CMakeLists.txt
new file mode 100644
index 00000000..0fd72475
--- /dev/null
+++ b/opentrack/CMakeLists.txt
@@ -0,0 +1,25 @@
+if(MSVC)
+ add_compile_options(-EHsc)
+ add_definitions(-D_HAS_EXCEPTIONS=1)
+endif()
+
+otr_module(executable EXECUTABLE BIN)
+
+set_target_properties(opentrack-executable PROPERTIES
+ SUFFIX "${opentrack-binary-suffix}"
+ OUTPUT_NAME "opentrack"
+ PREFIX ""
+)
+
+set_source_files_properties(resources.rc OBJECT_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/opentrack.ico")
+
+if(APPLE)
+ set_source_files_properties(appnap_mac.mm PROPERTIES COMPILE_FLAGS "-fno-objc-arc")
+ target_sources(${self} PRIVATE appnap_mac.mm)
+endif()
+
+target_link_libraries(${self} opentrack-user-interface opentrack-version)
+
+if(APPLE)
+ target_link_libraries(${self} "-framework Foundation")
+endif()
diff --git a/opentrack/appnap_mac.mm b/opentrack/appnap_mac.mm
new file mode 100644
index 00000000..3e0bea73
--- /dev/null
+++ b/opentrack/appnap_mac.mm
@@ -0,0 +1,47 @@
+#ifdef __APPLE__
+
+#import <Foundation/Foundation.h>
+
+/**
+ * Used to prevent macOS from throttling the opentrack process.
+ */
+
+id token = nil;
+
+void disable_appnap_start();
+void disable_appnap_stop();
+
+void disable_appnap_start() {
+
+ if(token){
+ NSLog(@"disable_appnap_start: already started");
+ return;
+ }
+
+
+ NSLog(@"disable_appnap_start");
+ token = [[NSProcessInfo processInfo]
+ beginActivityWithOptions: NSActivityUserInitiatedAllowingIdleSystemSleep
+ reason: @"Disable AppNap"];
+ [token retain];
+}
+
+void disable_appnap_stop() {
+ if(!token){
+ NSLog(@"disable_appnap_start: not started");
+ return;
+ }
+
+ NSLog(@"disable_appnap_stop");
+ [[NSProcessInfo processInfo] endActivity:token];
+ [token release];
+ token = nil;
+}
+
+
+
+#endif
+
+
+
+
diff --git a/opentrack/defs.hpp b/opentrack/defs.hpp
new file mode 100644
index 00000000..b2f0c467
--- /dev/null
+++ b/opentrack/defs.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+//#define UI_FORCED_TRACKER "pt"
+//#define UI_FORCED_FILTER "accela"
+
+//#define UI_NO_TRACKER_COMBOBOX
+//#define UI_NO_FILTER_COMBOBOX
+
+//#define UI_NO_TRACKER_SETTINGS_BUTTON
+//#define UI_NO_FILTER_SETTINGS_BUTTON
+
+//#define UI_NO_RAW_DATA
+//#define UI_NO_GAME_DATA
+//#define UI_NO_VIDEO_FEED
+
+//define UI_ACCELA_OLD_STAIRCASE
diff --git a/opentrack/lang/de_DE.ts b/opentrack/lang/de_DE.ts
new file mode 100644
index 00000000..6eaf28d4
--- /dev/null
+++ b/opentrack/lang/de_DE.ts
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>UI_new_config</name>
+ <message>
+ <source>Config filename</source>
+ <translation>Konfigurationsdatei</translation>
+ </message>
+ <message>
+ <source>New file name:</source>
+ <translation>Neuer Dateiname:</translation>
+ </message>
+</context>
+<context>
+ <name>main_window</name>
+ <message>
+ <source>Raw tracker data</source>
+ <translation>Rohe Tracker-Daten</translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation>Z</translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation>Nicken</translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation>Rollen</translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation>Gieren</translation>
+ </message>
+ <message>
+ <source>Game data</source>
+ <translation>Spieldaten</translation>
+ </message>
+ <message>
+ <source>Profile</source>
+ <translation>Profil</translation>
+ </message>
+ <message>
+ <source>Options</source>
+ <translation>Optionen</translation>
+ </message>
+ <message>
+ <source>Mapping</source>
+ <translation>Abbildung</translation>
+ </message>
+ <message>
+ <source>Tracking</source>
+ <translation>Tracking</translation>
+ </message>
+ <message>
+ <source>Start</source>
+ <translation>Starten</translation>
+ </message>
+ <message>
+ <source>Stop</source>
+ <translation>Stoppen</translation>
+ </message>
+ <message>
+ <source>Input</source>
+ <translation>Eingabe</translation>
+ </message>
+ <message>
+ <source>🔨</source>
+ <translation>🔨</translation>
+ </message>
+ <message>
+ <source>Output</source>
+ <translation>Ausgabe</translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation>Filter</translation>
+ </message>
+ <message>
+ <source>Create new empty config</source>
+ <translation>Neue leere Konfiguration erstellen</translation>
+ </message>
+ <message>
+ <source>Create new copied config</source>
+ <translation>Neue kopierte Konfiguration erstellen</translation>
+ </message>
+ <message>
+ <source>Open configuration directory</source>
+ <translation>Konfigurationsordner öffnen</translation>
+ </message>
+ <message>
+ <source>opentrack</source>
+ <translation>opentrack</translation>
+ </message>
+ <message>
+ <source> (debug)</source>
+ <translation> (Fehlersuche)</translation>
+ </message>
+ <message>
+ <source>Show the Octopus</source>
+ <translation>Den Oktopus anzeigen</translation>
+ </message>
+ <message>
+ <source>Hide the Octopus</source>
+ <translation>Den Oktopus verstecken</translation>
+ </message>
+ <message>
+ <source>Tracker settings</source>
+ <translation>Tracker-Einstellungen</translation>
+ </message>
+ <message>
+ <source>Filter settings</source>
+ <translation>Filter-Einstellungen</translation>
+ </message>
+ <message>
+ <source>Protocol settings</source>
+ <translation>Protokoll-Einstellungen</translation>
+ </message>
+ <message>
+ <source>Mappings</source>
+ <translation>Abbildungen</translation>
+ </message>
+ <message>
+ <source>Exit</source>
+ <translation>Beenden</translation>
+ </message>
+ <message>
+ <source>The Octopus is sad</source>
+ <translation>Der Oktopus ist traurig</translation>
+ </message>
+ <message>
+ <source>Check permissions for your .ini directory:
+
+&quot;%1&quot;%2
+
+Exiting now.</source>
+ <translation>Überprüfe die Berechtigungen des .ini-Ordners:
+
+&quot;%1&quot;%2
+
+Das Programm beendet sich nun.</translation>
+ </message>
+ <message>
+ <source> :: </source>
+ <translation> :: </translation>
+ </message>
+ <message>
+ <source>Running as root is bad</source>
+ <translation>Das Starten als root ist schlecht</translation>
+ </message>
+ <message>
+ <source>Do not run as root. Set correct device node permissions.</source>
+ <translation>Führe dieses Programm nicht als root aus. Setze die korrekten Gerätedatei-Berechtigungen.</translation>
+ </message>
+ <message>
+ <source>Running as root is bad, seriously</source>
+ <translation>Das Starten als root ist schlecht, ernsthaft</translation>
+ </message>
+ <message>
+ <source>Do not run as root. I&apos;ll keep whining at every startup.</source>
+ <translation>Führe dieses Programm nicht als root aus. Ich werde bei jedem Start rumheulen.</translation>
+ </message>
+ <message>
+ <source>Be annoyed, comprehensively.</source>
+ <translation>Sei verärgert, vollumfänglich.</translation>
+ </message>
+ <message>
+ <source>Don&apos;t run as root to remove these annoying messages.</source>
+ <translation>Führe das Programm nicht mehr als root aus, um diese ärgerliche Nachricht loszuwerden.</translation>
+ </message>
+</context>
+<context>
+ <name>new_file_dialog</name>
+ <message>
+ <source>File exists</source>
+ <translation>Die Datei existiert</translation>
+ </message>
+ <message>
+ <source>This file already exists. Pick another name.</source>
+ <translation>Diese Datei existiert bereits. Wähle einen anderen Namen.</translation>
+ </message>
+</context>
+</TS>
diff --git a/variant/default/lang/nl_NL.ts b/opentrack/lang/nl_NL.ts
index f9705855..7a465820 100644
--- a/variant/default/lang/nl_NL.ts
+++ b/opentrack/lang/nl_NL.ts
@@ -103,14 +103,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Running as root is really seriously bad</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Do not run as root. Be annoyed, comprehensively.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Create new empty config</source>
<translation type="unfinished"></translation>
</message>
@@ -174,6 +166,14 @@ Exiting now.</source>
<source> :: </source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Be annoyed, comprehensively.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Don&apos;t run as root to remove these annoying messages.</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>new_file_dialog</name>
diff --git a/variant/default/lang/ru_RU.ts b/opentrack/lang/ru_RU.ts
index 7a8ad82d..684a8212 100644
--- a/variant/default/lang/ru_RU.ts
+++ b/opentrack/lang/ru_RU.ts
@@ -171,11 +171,11 @@ Exiting now.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Running as root is really seriously bad</source>
+ <source>Be annoyed, comprehensively.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Do not run as root. Be annoyed, comprehensively.</source>
+ <source>Don&apos;t run as root to remove these annoying messages.</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/variant/default/lang/stub.ts b/opentrack/lang/stub.ts
index 83062231..04555dd0 100644
--- a/variant/default/lang/stub.ts
+++ b/opentrack/lang/stub.ts
@@ -103,14 +103,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Running as root is really seriously bad</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Do not run as root. Be annoyed, comprehensively.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Create new empty config</source>
<translation type="unfinished"></translation>
</message>
@@ -174,6 +166,14 @@ Exiting now.</source>
<source> :: </source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Be annoyed, comprehensively.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Don&apos;t run as root to remove these annoying messages.</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>new_file_dialog</name>
diff --git a/variant/default/lang/zh_CN.ts b/opentrack/lang/zh_CN.ts
index ca6bd74e..15609d70 100644
--- a/variant/default/lang/zh_CN.ts
+++ b/opentrack/lang/zh_CN.ts
@@ -103,11 +103,11 @@
<translation>请ä¸è¦ä»¥ç®¡ç†å‘˜è¿è¡Œã€‚è¿™è¯æ¯æ¬¡å¯åŠ¨æˆ‘éƒ½ä¼šå¼ºè°ƒä¸€é。</translation>
</message>
<message>
- <source>Running as root is really seriously bad</source>
+ <source>Don&apos;t run as root to remove these annoying messages.</source>
<translation>以管ç†å‘˜è¿è¡ŒçœŸçš„éžå¸¸ä¸å¥½</translation>
</message>
<message>
- <source>Do not run as root. Be annoyed, comprehensively.</source>
+ <source>Be annoyed, comprehensively.</source>
<translation>请ä¸è¦ä»¥ç®¡ç†å‘˜è¿è¡Œã€‚烦了å§ï¼Ÿèµ¶ç´§åŠ¨ä½œèµ·æ¥</translation>
</message>
<message>
diff --git a/opentrack/main-window.cpp b/opentrack/main-window.cpp
new file mode 100644
index 00000000..20506d81
--- /dev/null
+++ b/opentrack/main-window.cpp
@@ -0,0 +1,1140 @@
+/* Copyright (c) 2013-2016, Stanislaw Halik <sthalik@misaki.pl>
+
+ * 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 "main-window.hpp"
+#include "logic/pipeline.hpp"
+#include "options/options.hpp"
+#include "new_file_dialog.h"
+#include "migration/migration.hpp"
+#include "compat/check-visible.hpp"
+#include "compat/sleep.hpp"
+#include "compat/macros.h"
+#include "compat/library-path.hpp"
+#include "compat/math.hpp"
+#include "compat/sysexits.hpp"
+#include "opentrack/defs.hpp"
+
+#include <cstring>
+#include <utility>
+
+#include <QMessageBox>
+#include <QDesktopServices>
+#include <QDesktopWidget>
+#include <QApplication>
+
+#include <QFile>
+#include <QFileInfo>
+#include <QDir>
+#include <QDateTime>
+
+
+#ifdef __APPLE__
+void disable_appnap_start();
+void disable_appnap_stop();
+#endif
+
+extern "C" const char* const opentrack_version;
+
+using namespace options::globals;
+using namespace options;
+
+main_window::main_window() : State(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH)
+{
+ ui.setupUi(this);
+
+#if !defined _WIN32
+ annoy_if_root();
+#endif
+
+ adjustSize();
+ setWindowFlag(Qt::MSWindowsFixedSizeDialogHint);
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+
+ init_profiles();
+ init_buttons();
+ init_dylibs();
+ init_shortcuts();
+
+ init_tray_menu();
+ setVisible(!start_in_tray());
+ ensure_tray();
+
+ connect(&pose_update_timer, &QTimer::timeout,
+ this, &main_window::show_pose, Qt::DirectConnection);
+ connect(&det_timer, &QTimer::timeout,
+ this, &main_window::maybe_start_profile_from_executable);
+ det_timer.start(1000);
+ connect(&*s.b, &options::bundle_::reloading, this, &main_window::register_shortcuts);
+ connect(&*s.b, &options::bundle_::saving, this, &main_window::register_shortcuts);
+
+ ui.btnStartTracker->setFocus();
+#ifdef UI_NO_VIDEO_FEED
+ fake_video_frame.resize(640, 480);
+ fake_video_frame_parent.setVisible(false);
+#endif
+}
+
+void main_window::init_shortcuts()
+{
+ register_shortcuts();
+
+ // ctrl+q exits
+ connect(&kbd_quit, &QShortcut::activated, this, [this]() { main_window::exit(EXIT_SUCCESS); }, Qt::DirectConnection);
+ kbd_quit.setEnabled(true);
+}
+
+void main_window::init_dylibs()
+{
+ using dylib_ptr = Modules::dylib_ptr;
+ using dylib_list = Modules::dylib_list;
+
+ modules.filters().insert(modules.filters().begin(),
+ std::make_shared<dylib>("", dylib_type::Filter));
+
+#ifndef UI_NO_TRACKER_COMBOBOX
+ for (dylib_ptr& x : modules.trackers())
+ ui.iconcomboTrackerSource->addItem(x->icon, x->name, x->module_name);
+#endif
+
+ for (dylib_ptr& x : modules.protocols())
+ ui.iconcomboProtocol->addItem(x->icon, x->name, x->module_name);
+
+#ifndef UI_NO_FILTER_COMBOBOX
+ for (dylib_ptr& x : modules.filters())
+ ui.iconcomboFilter->addItem(x->icon, x->name, x->module_name);
+#endif
+
+#ifndef UI_NO_TRACKER_COMBOBOX
+ connect(ui.iconcomboTrackerSource, &QComboBox::currentTextChanged,
+ this, [this](const QString&) { pTrackerDialog = nullptr; if (options_widget) options_widget->tracker_module_changed(); });
+#endif
+
+ connect(ui.iconcomboProtocol, &QComboBox::currentTextChanged,
+ this, [this](const QString&) { pProtocolDialog = nullptr; if (options_widget) options_widget->proto_module_changed(); });
+
+#ifndef UI_NO_FILTER_COMBOBOX
+ connect(ui.iconcomboFilter, &QComboBox::currentTextChanged,
+ this, [this](const QString&) { pFilterDialog = nullptr; if (options_widget) options_widget->filter_module_changed(); });
+#endif
+
+ connect(&m.tracker_dll, value_::value_changed<QString>(),
+ this, &main_window::save_modules,
+ Qt::DirectConnection);
+
+ connect(&m.protocol_dll, value_::value_changed<QString>(),
+ this, &main_window::save_modules,
+ Qt::DirectConnection);
+
+ connect(&m.filter_dll, value_::value_changed<QString>(),
+ this, &main_window::save_modules,
+ Qt::DirectConnection);
+
+ {
+ struct list {
+ dylib_list& libs;
+ QComboBox* input;
+ value<QString>& place;
+ };
+
+ list types[] {
+#ifndef UI_NO_TRACKER_COMBOBOX
+ { modules.trackers(), ui.iconcomboTrackerSource, m.tracker_dll },
+#endif
+ { modules.protocols(), ui.iconcomboProtocol, m.protocol_dll },
+#ifndef UI_NO_FILTER_COMBOBOX
+ { modules.filters(), ui.iconcomboFilter, m.filter_dll },
+#endif
+ };
+
+ for (list& type : types)
+ {
+ list t = type;
+ tie_setting(t.place, t.input,
+ [t](const QString& name) {
+ auto [ptr, idx] = module_by_name(name, t.libs);
+ return idx;
+ },
+ [t](int, const QVariant& userdata) {
+ auto [ptr, idx] = module_by_name(userdata.toString(), t.libs);
+ if (ptr)
+ return ptr->module_name;
+ else
+ return QString();
+ });
+ }
+ }
+}
+
+void main_window::init_profiles()
+{
+ copy_presets();
+ refresh_profile_list();
+ // implicitly created by `ini_directory()'
+ if (ini_directory().isEmpty() || !QDir(ini_directory()).isReadable())
+ die_on_profile_not_writable();
+
+ set_profile(ini_filename());
+
+ // profile menu
+ profile_menu.addAction(tr("Create new empty config"), this, &main_window::create_empty_profile);
+ profile_menu.addAction(tr("Create new copied config"), this, &main_window::create_copied_profile);
+ profile_menu.addAction(tr("Open configuration directory"), this, &main_window::open_profile_directory);
+ ui.profile_button->setMenu(&profile_menu);
+
+ connect(&profile_list_timer, &QTimer::timeout, this, &main_window::refresh_profile_list);
+ profile_list_timer.start(1000 * 5);
+
+ connect(ui.iconcomboProfile, &QComboBox::currentTextChanged,
+ this, [this](const QString& x) { main_window::set_profile(x); });
+}
+
+void main_window::init_tray_menu()
+{
+ tray_menu.clear();
+
+ QString display_name(opentrack_version);
+ if (display_name.startsWith("opentrack-"))
+ display_name = tr("opentrack") + " " + display_name.mid(sizeof("opentrack-") - 1);
+ if (display_name.endsWith("-DEBUG"))
+ display_name.replace(display_name.size() - int(sizeof("DEBUG")), display_name.size(), tr(" (debug)"));
+
+ menu_action_header.setEnabled(false);
+ menu_action_header.setText(display_name);
+ menu_action_header.setIcon(QIcon(":/images/opentrack.png"));
+ tray_menu.addAction(&menu_action_header);
+
+ menu_action_show.setIconVisibleInMenu(true);
+ menu_action_show.setText(isHidden() ? tr("Show the Octopus") : tr("Hide the Octopus"));
+ menu_action_show.setIcon(QIcon(":/images/opentrack.png"));
+ QObject::connect(&menu_action_show, &QAction::triggered, this, [this] { toggle_restore_from_tray(QSystemTrayIcon::Trigger); });
+ tray_menu.addAction(&menu_action_show);
+
+ tray_menu.addSeparator();
+
+ menu_action_tracker.setText(tr("Tracker settings"));
+ menu_action_tracker.setIcon(QIcon(":/images/tools.png"));
+ QObject::connect(&menu_action_tracker, &QAction::triggered, this, &main_window::show_tracker_settings);
+ tray_menu.addAction(&menu_action_tracker);
+
+ menu_action_filter.setText(tr("Filter settings"));
+ menu_action_filter.setIcon(QIcon(":/images/filter-16.png"));
+ QObject::connect(&menu_action_filter, &QAction::triggered, this, &main_window::show_filter_settings);
+ tray_menu.addAction(&menu_action_filter);
+
+ menu_action_proto.setText(tr("Protocol settings"));
+ menu_action_proto.setIcon(QIcon(":/images/settings16.png"));
+ QObject::connect(&menu_action_proto, &QAction::triggered, this, &main_window::show_proto_settings);
+ tray_menu.addAction(&menu_action_proto);
+
+ tray_menu.addSeparator();
+
+ menu_action_mappings.setIcon(QIcon(":/images/curves.png"));
+ menu_action_mappings.setText(tr("Mappings"));
+ QObject::connect(&menu_action_mappings, &QAction::triggered, this, &main_window::show_mapping_window);
+ tray_menu.addAction(&menu_action_mappings);
+
+ menu_action_options.setIcon(QIcon(":/images/tools.png"));
+ menu_action_options.setText(tr("Options"));
+ QObject::connect(&menu_action_options, &QAction::triggered, this, [this] { show_options_dialog(true); });
+ tray_menu.addAction(&menu_action_options);
+
+ tray_menu.addSeparator();
+
+ menu_action_exit.setText(tr("Exit"));
+ QObject::connect(&menu_action_exit, &QAction::triggered, this, &main_window::exit);
+ tray_menu.addAction(&menu_action_exit);
+
+ connect(&s.tray_enabled, value_::value_changed<bool>(),
+ this, &main_window::ensure_tray);
+}
+
+void main_window::init_buttons()
+{
+ update_button_state(false, false);
+ connect(ui.btnEditCurves, &QPushButton::clicked, this, &main_window::show_mapping_window);
+ connect(ui.btnShortcuts, &QPushButton::clicked, this, [this] { show_options_dialog(true); });
+#ifndef UI_NO_TRACKER_SETTINGS_BUTTON
+ connect(ui.btnShowEngineControls, &QPushButton::clicked, this, &main_window::show_tracker_settings);
+#endif
+ connect(ui.btnShowServerControls, &QPushButton::clicked, this, &main_window::show_proto_settings);
+#ifndef UI_NO_FILTER_SETTINGS_BUTTON
+ connect(ui.btnShowFilterControls, &QPushButton::clicked, this, &main_window::show_filter_settings);
+#endif
+ connect(ui.btnStartTracker, &QPushButton::clicked, this, &main_window::start_tracker_);
+ connect(ui.btnStopTracker, &QPushButton::clicked, this, &main_window::stop_tracker_);
+}
+
+void main_window::register_shortcuts()
+{
+ global_shortcuts.reload({
+ { s.key_start_tracking1, [this](bool) { start_tracker(); }, true },
+ { s.key_start_tracking2, [this](bool) { start_tracker(); }, true },
+
+ { s.key_stop_tracking1, [this](bool) { stop_tracker(); }, true },
+ { s.key_stop_tracking2, [this](bool) { stop_tracker(); }, true },
+
+ { s.key_toggle_tracking1, [this](bool) { toggle_tracker(); }, true },
+ { s.key_toggle_tracking2, [this](bool) { toggle_tracker(); }, true },
+
+ { s.key_restart_tracking1, [this](bool) { restart_tracker(); }, true },
+ { s.key_restart_tracking2, [this](bool) { restart_tracker(); }, true },
+ });
+
+ if (work)
+ work->reload_shortcuts();
+}
+
+void main_window::die_on_profile_not_writable()
+{
+ stop_tracker_();
+
+ static const QString pad(16, QChar(' '));
+
+ QMessageBox::critical(this,
+ tr("The Octopus is sad"),
+ tr("Check permissions for your .ini directory:\n\n\"%1\"%2\n\nExiting now.").arg(ini_directory(), pad),
+ QMessageBox::Close, QMessageBox::NoButton);
+
+ exit(EX_OSFILE);
+}
+
+bool main_window::profile_name_from_dialog(QString& ret)
+{
+ new_file_dialog dlg;
+ dlg.exec();
+ return dlg.is_ok(ret);
+}
+
+main_window::~main_window()
+{
+ // stupid ps3 eye has LED issues
+#ifndef UI_NO_VIDEO_FEED
+ if (work && ui.video_frame->layout())
+#else
+ if (work)
+#endif
+ {
+ hide();
+ stop_tracker_();
+ close();
+
+ portable::sleep(1000);
+ }
+
+ exit();
+}
+
+void main_window::save_modules()
+{
+ m.b->save();
+}
+
+void main_window::create_empty_profile()
+{
+ QString name;
+ if (profile_name_from_dialog(name))
+ {
+ (void)create_profile_from_preset(name);
+ refresh_profile_list();
+
+ if (profile_list.contains(name))
+ {
+ QSignalBlocker q(ui.iconcomboProfile);
+
+ set_profile(name, false);
+ mark_profile_as_not_needing_migration();
+ }
+ }
+}
+
+void main_window::create_copied_profile()
+{
+ const QString cur = ini_pathname();
+ QString name;
+ if (!cur.isEmpty() && profile_name_from_dialog(name))
+ {
+ const QString new_name = ini_combine(name);
+ (void) QFile::remove(new_name);
+ QFile::copy(cur, new_name);
+
+ refresh_profile_list();
+
+ if (profile_list.contains(name))
+ {
+ QSignalBlocker q(ui.iconcomboProfile);
+
+ set_profile(name, false);
+ mark_profile_as_not_needing_migration();
+ }
+ }
+
+}
+
+void main_window::open_profile_directory()
+{
+ QDesktopServices::openUrl("file:///" + QDir::toNativeSeparators(ini_directory()));
+}
+
+void main_window::refresh_profile_list()
+{
+ if (work)
+ return;
+
+ QStringList list = ini_list();
+ QString current = ini_filename();
+
+ if (list == profile_list)
+ return;
+
+ if (!list.contains(current))
+ current = OPENTRACK_DEFAULT_PROFILE;
+
+ profile_list = list;
+
+ static const QIcon icon(":/images/settings16.png");
+
+ QSignalBlocker l(ui.iconcomboProfile);
+
+ ui.iconcomboProfile->clear();
+ ui.iconcomboProfile->addItems(list);
+
+ for (int i = 0; i < list.size(); i++)
+ ui.iconcomboProfile->setItemIcon(i, icon);
+
+ ui.iconcomboProfile->setCurrentText(current);
+}
+
+
+
+void main_window::update_button_state(bool running, bool inertialp)
+{
+ bool not_running = !running;
+ ui.iconcomboProfile->setEnabled(not_running);
+ ui.btnStartTracker->setEnabled(not_running);
+ ui.btnStopTracker->setEnabled(running);
+ ui.iconcomboProtocol->setEnabled(not_running);
+#ifndef UI_NO_FILTER_COMBOBOX
+ ui.iconcomboFilter->setEnabled(not_running);
+#endif
+#ifndef UI_NO_TRACKER_COMBOBOX
+ ui.iconcomboTrackerSource->setEnabled(not_running);
+#endif
+ ui.profile_button->setEnabled(not_running);
+#ifndef UI_NO_VIDEO_FEED
+ ui.video_frame_label->setVisible(not_running || inertialp);
+ if(not_running)
+ {
+ ui.video_frame_label->setPixmap(QPixmap(":/images/tracking-not-started.png"));
+ }
+ else {
+ ui.video_frame_label->setPixmap(QPixmap(":/images/no-feed.png"));
+ }
+#endif
+}
+
+void main_window::start_tracker_()
+{
+ if (work)
+ return;
+
+#ifdef __APPLE__
+ disable_appnap_start();
+#endif
+
+
+#ifndef UI_NO_VIDEO_FEED
+ auto* frame = ui.video_frame;
+#else
+ auto* frame = &fake_video_frame;
+#endif
+ work = std::make_shared<Work>(pose, frame, current_tracker(), current_protocol(), current_filter());
+
+ if (!work->is_ok())
+ {
+ work = nullptr;
+ return;
+ }
+
+ {
+ double p[6] = {0,0,0, 0,0,0};
+ show_pose_(p, p);
+ }
+
+ if (pTrackerDialog)
+ pTrackerDialog->register_tracker(&*work->libs.pTracker);
+
+ if (pFilterDialog && work->libs.pFilter)
+ pFilterDialog->register_filter(&*work->libs.pFilter);
+
+ if (pProtocolDialog)
+ pProtocolDialog->register_protocol(&*work->libs.pProtocol);
+
+ if (options_widget)
+ {
+ options_widget->register_tracker(&*work->libs.pTracker);
+ options_widget->register_protocol(&*work->libs.pProtocol);
+ if (work->libs.pFilter)
+ options_widget->register_filter(&*work->libs.pFilter);
+ }
+
+ pose_update_timer.start(1000/30);
+
+ // NB check valid since SelectedLibraries ctor called
+ // trackers take care of layout state updates
+ const bool is_inertial = frame->layout() == nullptr;
+ update_button_state(true, is_inertial);
+
+ ui.btnStopTracker->setFocus();
+}
+
+void main_window::stop_tracker_()
+{
+ if (!work)
+ return;
+
+#ifdef __APPLE__
+ disable_appnap_stop();
+#endif
+
+ force_is_visible(true);
+ with_tracker_teardown sentinel;
+
+ pose_update_timer.stop();
+ ui.pose_display->present(0,0,0, 0,0,0);
+
+ if (options_widget)
+ {
+ // XXX TODO other module types
+ options_widget->unregister_tracker();
+ }
+
+ if (pTrackerDialog)
+ pTrackerDialog->unregister_tracker();
+
+ if (pProtocolDialog)
+ pProtocolDialog->unregister_protocol();
+
+ if (pFilterDialog)
+ pFilterDialog->unregister_filter();
+
+ work = nullptr;
+
+ {
+ double p[6] {};
+ show_pose_(p, p);
+ }
+
+ update_button_state(false, false);
+ set_title();
+ ui.btnStartTracker->setFocus();
+}
+
+void main_window::show_pose_(const double* mapped, const double* raw)
+{
+ ui.pose_display->present(mapped[Yaw], mapped[Pitch], -mapped[Roll],
+ mapped[TX], mapped[TY], mapped[TZ]);
+
+#ifndef UI_NO_RAW_DATA
+ QLCDNumber* raw_[] = {
+ ui.raw_x, ui.raw_y, ui.raw_z,
+ ui.raw_yaw, ui.raw_pitch, ui.raw_roll,
+ };
+#endif
+#ifndef UI_NO_GAME_DATA
+ QLCDNumber* mapped_[] = {
+ ui.pose_x, ui.pose_y, ui.pose_z,
+ ui.pose_yaw, ui.pose_pitch, ui.pose_roll,
+ };
+#endif
+
+#if !defined UI_NO_RAW_DATA || !defined UI_NO_GAME_DATA
+ for (int k = 0; k < 6; k++)
+#endif
+ {
+#ifndef UI_NO_RAW_DATA
+ raw_[k]->display(iround(raw[k]));
+#endif
+#ifndef UI_NO_GAME_DATA
+ mapped_[k]->display(iround(mapped[k]));
+#endif
+ }
+
+ QString game_title;
+ if (work && work->libs.pProtocol)
+ game_title = work->libs.pProtocol->game_name();
+ set_title(game_title);
+}
+
+void main_window::set_title(const QString& game_title)
+{
+ static const QString version{opentrack_version};
+ static const QString sep { tr(" :: ") };
+ static const QString pat1{ version + sep + "%1" + sep + "%2" };
+ static const QString pat2{ version + sep + "%1" };
+
+ const QString current = ini_filename();
+
+ if (game_title.isEmpty())
+ setWindowTitle(pat2.arg(current));
+ else
+ setWindowTitle(pat1.arg(current, game_title));
+}
+
+void main_window::show_pose()
+{
+ set_is_visible(*this);
+
+ if (mapping_widget)
+ mapping_widget->refresh_tab();
+
+ if (!check_is_visible())
+ return;
+
+ double mapped[6], raw[6];
+
+ work->pipeline_.raw_and_mapped_pose(mapped, raw);
+
+ show_pose_(mapped, raw);
+}
+
+bool main_window::module_tabs_enabled() const
+{
+ return true;
+#if 0
+ enum module_tab_state { tabs_maybe = -1, tabs_disable, tabs_enable };
+
+ static const auto force = progn(
+ auto str = getenv("OPENTRACK_MODULE_TABS");
+ if (!str || !*str)
+ return tabs_maybe;
+ constexpr const char* strings_for_false[] = {
+ "0", "n", "f", "disable",
+ };
+ constexpr const char* strings_for_true[] = {
+ "1", "y", "t", "enable",
+ };
+ for (const auto* x : strings_for_false)
+ if (!strncasecmp(str, x, strlen(x)))
+ return tabs_disable;
+ for (const auto* x : strings_for_true)
+ if (!strncasecmp(str, x, strlen(x)))
+ return tabs_enable;
+ qDebug() << "main-window: invalid boolean for OPENTRACK_MODULE_TABS:"
+ << QLatin1String{str};
+ return tabs_maybe;
+ );
+ switch (force)
+ {
+ case tabs_disable: return false;
+ case tabs_enable: return true;
+ case tabs_maybe: (void)0;
+ }
+ auto* d = QApplication::desktop();
+ if (!d)
+ return false;
+ // Windows 10: 40px, Windows 11: 48px, KDE: 51px
+ constexpr int taskbar_size = 51;
+ constexpr int min_avail_height = 768 - taskbar_size;
+ QRect rect = d->availableGeometry(this);
+ return rect.height() >= min_avail_height;
+#endif
+}
+
+static void show_window(QWidget& d, bool fresh)
+{
+ if (fresh)
+ {
+ d.setWindowFlag(Qt::MSWindowsFixedSizeDialogHint);
+ d.adjustSize();
+ d.setFixedSize(d.size());
+ d.show();
+#ifdef __APPLE__
+ d.raise();
+#endif
+ d.activateWindow();
+ }
+ else
+ {
+ d.show();
+#ifdef __APPLE__
+ d.raise();
+#endif
+ d.activateWindow();
+ }
+}
+
+template<typename t, typename... Args>
+static bool mk_window(std::unique_ptr<t>& d, bool show, Args&&... params)
+{
+ bool fresh = false;
+
+ if (!(d && d->isVisible()))
+ {
+ d = std::make_unique<t>(std::forward<Args>(params)...);
+ fresh = !!d;
+ }
+
+ if (d)
+ {
+ if (show && !d->embeddable())
+ show_window(*d, fresh);
+ }
+
+ return fresh;
+}
+
+template<typename Instance, typename Dialog>
+static void show_module_settings(std::shared_ptr<Instance> instance,
+ std::unique_ptr<Dialog>& dialog,
+ const Modules::dylib_ptr& lib,
+ std::unique_ptr<options_dialog>& options_widget,
+ main_window* win,
+ bool show,
+ void(Dialog::*register_fun)(Instance*),
+ void(options_dialog::*switch_tab_fun)())
+{
+ if (!lib || !lib->Dialog)
+ return;
+
+ bool fresh = !(dialog && dialog->isVisible());
+ if (fresh)
+ dialog = std::unique_ptr<Dialog>{(Dialog*)lib->Dialog()};
+ bool embed = dialog->embeddable() && win->module_tabs_enabled();
+
+ if (!embed)
+ {
+ if (fresh)
+ {
+ if (instance)
+ ((*dialog).*register_fun)(&*instance);
+ }
+ if (show)
+ show_window(*dialog, fresh);
+ }
+ else if (show)
+ {
+ bool fresh = !options_widget;
+ win->show_options_dialog(false);
+ ((*options_widget).*switch_tab_fun)();
+ show_window(*options_widget, fresh);
+ }
+}
+
+void main_window::show_tracker_settings_(bool show)
+{
+ show_module_settings(work ? work->libs.pTracker : nullptr, pTrackerDialog, current_tracker(),
+ options_widget, this, show,
+ &ITrackerDialog::register_tracker, &options_dialog::switch_to_tracker_tab);
+}
+
+void main_window::show_proto_settings_(bool show)
+{
+ show_module_settings(work ? work->libs.pProtocol : nullptr, pProtocolDialog, current_protocol(),
+ options_widget, this, show,
+ &IProtocolDialog::register_protocol, &options_dialog::switch_to_proto_tab);
+}
+
+void main_window::show_filter_settings_(bool show)
+{
+ show_module_settings(work ? work->libs.pFilter : nullptr, pFilterDialog, current_filter(),
+ options_widget, this, show,
+ &IFilterDialog::register_filter, &options_dialog::switch_to_filter_tab);
+}
+
+void main_window::show_options_dialog(bool show)
+{
+ if (options_widget && options_widget->isVisible())
+ {
+ if (show)
+ show_window(*options_widget, false);
+ return;
+ }
+
+ bool embed = module_tabs_enabled();
+
+ if (embed)
+ {
+ show_tracker_settings_(false);
+ show_proto_settings_(false);
+ show_filter_settings_(false);
+ }
+
+ // make them into unique_ptr<BaseDialog>
+ std::unique_ptr<ITrackerDialog> empty_ITD;
+ std::unique_ptr<IProtocolDialog> empty_IPD;
+ std::unique_ptr<IFilterDialog> empty_IFD;
+
+ mk_window(options_widget, false,
+ embed ? pTrackerDialog : empty_ITD,
+ embed ? pProtocolDialog : empty_IPD,
+ embed ? pFilterDialog : empty_IFD,
+ [this](bool flag) { set_keys_enabled(!flag); });
+
+ if (work)
+ {
+ if (work->libs.pTracker)
+ options_widget->register_tracker(&*work->libs.pTracker);
+ if (work->libs.pProtocol)
+ options_widget->register_protocol(&*work->libs.pProtocol);
+ if (work->libs.pFilter)
+ options_widget->register_filter(&*work->libs.pFilter);
+ }
+
+ if (show)
+ show_window(*options_widget, true);
+}
+
+void main_window::show_mapping_window()
+{
+ mk_window(mapping_widget, true, pose);
+}
+
+void main_window::exit(int status)
+{
+ // don't use std::call_once here, leads to freeze in Microsoft's CRT
+ // this function never needs reentrancy anyway
+
+ // this is probably harmless, but better safe than sorry
+ if (exiting_already)
+ return;
+ exiting_already = true;
+
+ //qDebug() << "opentrack: exiting";
+
+ if (tray)
+ tray->hide();
+ tray = nullptr;
+
+ //close();
+ QApplication::setQuitOnLastWindowClosed(true);
+ QApplication::exit(status);
+}
+
+void main_window::set_profile(const QString& new_name_, bool migrate)
+{
+ QSignalBlocker b(ui.iconcomboProfile);
+
+ QString new_name = new_name_;
+ bool do_refresh = false;
+
+ if (!profile_list.contains(new_name_))
+ {
+ new_name = QStringLiteral(OPENTRACK_DEFAULT_PROFILE);
+ if (!profile_list.contains(new_name))
+ {
+ migrate = false;
+ do_refresh = true;
+ }
+ }
+
+ if (create_profile_from_preset(new_name))
+ migrate = true;
+
+ if (do_refresh)
+ refresh_profile_list();
+ assert(profile_list.contains(new_name));
+
+ const bool status = new_name != ini_filename();
+
+ if (status)
+ set_profile_in_registry(new_name);
+
+ using bundler = options::detail::bundler;
+
+ bundler::reload_no_notify();
+
+ if (migrate)
+ // migrations are for config layout changes and other user-visible
+ // incompatibilities in future versions
+ run_migrations();
+ else
+ mark_profile_as_not_needing_migration();
+
+ bundler::notify();
+
+ set_title();
+
+ if (status)
+ ui.iconcomboProfile->setCurrentText(new_name);
+}
+
+void main_window::ensure_tray()
+{
+ if (!tray_enabled())
+ {
+ if (!isVisible())
+ {
+ show();
+
+#ifdef __APPLE__
+ raise(); // for OSX
+#endif
+ activateWindow(); // for Windows
+ }
+
+ if (tray)
+ tray->hide();
+ tray = nullptr;
+
+ QApplication::setQuitOnLastWindowClosed(true);
+ }
+ else
+ {
+ if (!tray)
+ {
+ tray = std::make_unique<QSystemTrayIcon>(this);
+ tray->setIcon(QIcon(":/images/opentrack.png"));
+ tray->setContextMenu(&tray_menu);
+ tray->show();
+
+ connect(&*tray, &QSystemTrayIcon::activated,
+ this, &main_window::toggle_restore_from_tray);
+ }
+
+ QApplication::setQuitOnLastWindowClosed(false);
+ }
+}
+
+void main_window::toggle_restore_from_tray(QSystemTrayIcon::ActivationReason e)
+{
+ switch (e)
+ {
+ // if we enable double click also then it causes
+ // toggle back to the original state
+ //case QSystemTrayIcon::DoubleClick:
+ case QSystemTrayIcon::Trigger: // single click
+ break;
+ default:
+ return;
+ }
+
+ ensure_tray();
+
+ const bool is_minimized = isHidden() || !tray_enabled();
+
+ menu_action_show.setText(!isHidden() ? tr("Show the Octopus") : tr("Hide the Octopus"));
+
+ setVisible(is_minimized);
+
+ if (is_minimized)
+ {
+#ifdef __APPLE__
+ raise(); // for OSX
+#endif
+ activateWindow(); // for Windows
+ }
+ else
+ {
+ lower();
+ clearFocus();
+ }
+}
+
+bool main_window::maybe_hide_to_tray(QEvent* e)
+{
+ if (e->type() == QEvent::WindowStateChange &&
+ (windowState() & Qt::WindowMinimized) &&
+ tray_enabled())
+ {
+ e->accept();
+ ensure_tray();
+ hide();
+
+ return true;
+ }
+
+ return false;
+}
+
+void main_window::closeEvent(QCloseEvent*)
+{
+ exit();
+}
+
+void main_window::maybe_start_profile_from_executable()
+{
+ if (!work)
+ {
+ QString profile;
+ if (det.profile_to_start(profile) && profile_list.contains(profile))
+ {
+ set_profile(profile);
+ start_tracker_();
+ }
+ }
+ else
+ {
+ if (det.should_stop())
+ stop_tracker_();
+ }
+}
+
+void main_window::set_keys_enabled(bool flag)
+{
+ if (!flag)
+ {
+ if (work)
+ work->sc.reload({});
+ global_shortcuts.reload({});
+ }
+ else
+ register_shortcuts();
+}
+
+void main_window::changeEvent(QEvent* e)
+{
+ if (!maybe_hide_to_tray(e))
+ e->ignore();
+}
+
+bool main_window::event(QEvent* event)
+{
+ using t = QEvent::Type;
+
+ if (work)
+ {
+ switch (event->type())
+ {
+ case t::Hide:
+ case t::WindowActivate:
+ case t::WindowDeactivate:
+ case t::WindowStateChange:
+ case t::FocusIn:
+ set_is_visible(*this, true);
+ break;
+ default:
+ break;
+ }
+ }
+ return QMainWindow::event(event);
+}
+
+bool main_window::tray_enabled()
+{
+ return s.tray_enabled && QSystemTrayIcon::isSystemTrayAvailable();
+}
+
+bool main_window::start_in_tray()
+{
+ return tray_enabled() && s.tray_start;
+}
+
+void main_window::set_profile_in_registry(const QString &profile)
+{
+ with_global_settings_object([&](QSettings& s) {
+ s.setValue(OPENTRACK_PROFILE_FILENAME_KEY, profile);
+ mark_global_ini_modified();
+ });
+}
+
+void main_window::restart_tracker_()
+{
+ qDebug() << "restart tracker";
+
+ stop_tracker_();
+ start_tracker_();
+}
+
+void main_window::toggle_tracker_()
+{
+ qDebug() << "toggle tracker";
+
+ if (work)
+ stop_tracker_();
+ else
+ start_tracker_();
+}
+
+void main_window::copy_presets()
+{
+ const QString preset_dir = library_path + "/presets/";
+ const QDir dir{preset_dir};
+ if (!dir.exists())
+ {
+ qDebug() << "no preset dir";
+ return;
+ }
+ with_global_settings_object([&](QSettings& s) {
+ const QString& key = QStringLiteral("last-preset-copy-time");
+ const auto last_time = s.value(key, -1LL).toLongLong();
+ for (const auto& file : dir.entryInfoList({ "*.ini" }, QDir::Files, QDir::Name))
+ {
+ if (file.fileName() == QStringLiteral("default.ini"))
+ continue;
+ if (last_time < file.lastModified().toSecsSinceEpoch())
+ {
+ qDebug() << "copy preset" << file.fileName();
+ (void)QFile::copy(file.filePath(), ini_combine(file.fileName()));
+ }
+ }
+ s.setValue(key, QDateTime::currentSecsSinceEpoch());
+ });
+}
+
+bool main_window::create_profile_from_preset(const QString& name)
+{
+ const QString dest = ini_combine(name);
+
+ if (QFile::exists(dest))
+ return false;
+
+ const QString& default_name = QStringLiteral(OPENTRACK_DEFAULT_PROFILE);
+ const QString default_preset = (library_path + "/presets/%1").arg(default_name);
+
+ if (QFile::exists(default_preset))
+ {
+ bool ret = QFile::copy(default_preset, dest);
+ if (ret)
+ qDebug() << "create profile" << name << "from default preset" << (ret ? "" : "FAILED!");
+ return ret;
+ }
+ else
+ {
+ (void)QFile(ini_combine(name)).open(QFile::ReadWrite);
+ return false;
+ }
+}
+
+#if !defined _WIN32
+# include <unistd.h>
+void main_window::annoy_if_root()
+{
+ if (geteuid() == 0)
+ {
+ struct lst {
+ QString caption;
+ QString msg;
+ int sleep_ms;
+ };
+
+ const lst list[] = {
+ {
+ tr("Running as root is bad"),
+ tr("Do not run as root. Set correct device node permissions."),
+ 1000,
+ },
+ {
+ tr("Running as root is bad, seriously"),
+ tr("Do not run as root. I'll keep whining at every startup."),
+ 3000,
+ },
+ {
+ tr("Be annoyed, comprehensively."),
+ tr("Don't run as root to remove these annoying messages."),
+ 0
+ }
+ };
+
+ for (const auto& x : list)
+ {
+ QMessageBox::critical(this, x.caption, x.msg, QMessageBox::Ok);
+ portable::sleep(x.sleep_ms);
+ }
+ }
+}
+#endif
diff --git a/variant/default/main-window.hpp b/opentrack/main-window.hpp
index c28aadc4..1dcbd0eb 100644
--- a/variant/default/main-window.hpp
+++ b/opentrack/main-window.hpp
@@ -8,9 +8,10 @@
#pragma once
+#include "opentrack/defs.hpp"
#include "api/plugin-support.hpp"
#include "gui/mapping-dialog.hpp"
-#include "gui/settings.hpp"
+#include "gui/options-dialog.hpp"
#include "gui/process_detector.h"
#include "logic/main-settings.hpp"
#include "logic/pipeline.hpp"
@@ -18,44 +19,46 @@
#include "logic/work.hpp"
#include "logic/state.hpp"
#include "options/options.hpp"
+#include "compat/qt-signal.hpp"
-#include <QApplication>
#include <QMainWindow>
#include <QKeySequence>
#include <QShortcut>
-#include <QPixmap>
#include <QTimer>
#include <QSystemTrayIcon>
#include <QString>
#include <QMenu>
#include <QAction>
-#include <QEvent>
-#include <QCloseEvent>
+#include <QList>
-#include <vector>
-#include <tuple>
#include <memory>
#include "ui_main-window.h"
-using namespace options;
-
-class main_window : public QMainWindow, private State
+class main_window final : public QMainWindow, private State
{
- Q_OBJECT
+ Q_DECLARE_TR_FUNCTIONS(main_window)
Ui::main_window ui;
- Shortcuts global_shortcuts;
- module_settings m;
std::unique_ptr<QSystemTrayIcon> tray;
- QMenu tray_menu;
+ QMenu tray_menu { this };
+
QTimer pose_update_timer { this };
QTimer det_timer;
- QTimer config_list_timer;
+ QTimer profile_list_timer;
+
+ Shortcuts global_shortcuts;
+ QShortcut kbd_quit { QKeySequence("Ctrl+Q"), this };
+
+#ifdef UI_NO_VIDEO_FEED
+ QWidget fake_video_frame_parent;
+ QFrame fake_video_frame{&fake_video_frame_parent};
+#endif
+
std::unique_ptr<options_dialog> options_widget;
std::unique_ptr<mapping_dialog> mapping_widget;
- QShortcut kbd_quit { QKeySequence("Ctrl+Q"), this };
+
std::unique_ptr<IFilterDialog> pFilterDialog;
std::unique_ptr<IProtocolDialog> pProtocolDialog;
std::unique_ptr<ITrackerDialog> pTrackerDialog;
@@ -63,6 +66,8 @@ class main_window : public QMainWindow, private State
process_detector_worker det;
QMenu profile_menu;
+ QList<QString> profile_list;
+
QAction menu_action_header { &tray_menu },
menu_action_show { &tray_menu },
menu_action_exit { &tray_menu },
@@ -72,88 +77,77 @@ class main_window : public QMainWindow, private State
menu_action_options { &tray_menu },
menu_action_mappings { &tray_menu };
- std::shared_ptr<dylib> current_tracker()
- {
- return modules.trackers().value(ui.iconcomboTrackerSource->currentIndex(), nullptr);
- }
- std::shared_ptr<dylib> current_protocol()
- {
- return modules.protocols().value(ui.iconcomboProtocol->currentIndex(), nullptr);
- }
- std::shared_ptr<dylib> current_filter()
- {
- return modules.filters().value(ui.iconcomboFilter->currentIndex(), nullptr);
- }
+ bool exiting_already { false };
- void update_button_state(bool running, bool inertialp);
- void display_pose(const double* mapped, const double* raw);
- void set_title(const QString& game_title = QString());
- static bool get_new_config_name_from_dialog(QString &ret);
- void set_profile_in_registry(const QString& profile);
+ qt_signal<void> start_tracker { this, &main_window::start_tracker_, Qt::QueuedConnection };
+ qt_signal<void> stop_tracker { this, &main_window::stop_tracker_, Qt::QueuedConnection };
+ qt_signal<void> toggle_tracker { this, &main_window::toggle_tracker_, Qt::QueuedConnection };
+ qt_signal<void> restart_tracker { this, &main_window::restart_tracker_, Qt::QueuedConnection };
+
+public:
+ void init_dylibs();
+ void init_tray_menu();
+ void init_profiles();
+ void init_buttons();
+
+ void init_shortcuts();
void register_shortcuts();
void set_keys_enabled(bool flag);
- bool is_config_listed(const QString& name);
- void init_tray();
+ void update_button_state(bool running, bool inertialp);
- void changeEvent(QEvent* e) override;
- bool event(QEvent *event) override;
- bool maybe_hide_to_tray(QEvent* e);
-#if !defined _WIN32 && !defined __APPLE__
+#if !defined _WIN32
void annoy_if_root();
#endif
- // only use in impl file since no definition in header!
- template<typename t>
- bool mk_dialog(std::shared_ptr<dylib> lib, std::unique_ptr<t>& d);
-
- // idem
- template<typename t, typename... Args>
- inline bool mk_window(std::unique_ptr<t>& place, Args&&... params);
-
- // idem
- template<typename t, typename F>
- bool mk_window_common(std::unique_ptr<t>& d, F&& ctor);
-
+ void changeEvent(QEvent* e) override;
+ bool maybe_hide_to_tray(QEvent* e);
void closeEvent(QCloseEvent *event) override;
+ bool event(QEvent *event) override;
-private slots:
- void save_modules();
- void exit(int status = EXIT_SUCCESS);
- bool set_profile(const QString& new_name);
+ void show_tracker_settings_(bool show);
+ void show_proto_settings_(bool show);
+ void show_filter_settings_(bool show);
+ void show_tracker_settings() { show_tracker_settings_(true); }
+ void show_proto_settings() { show_proto_settings_(true); }
+ void show_filter_settings() { show_filter_settings_(true); }
- void show_tracker_settings();
- void show_proto_settings();
- void show_filter_settings();
- void show_options_dialog();
+ void show_options_dialog(bool show);
void show_mapping_window();
- void show_pose();
-
- void maybe_start_profile_from_executable();
- void make_empty_config();
- void make_copied_config();
- void open_config_directory();
- bool refresh_config_list();
+ void show_pose();
+ void show_pose_(const double* mapped, const double* raw);
+ void set_title(const QString& game_title = QString());
void start_tracker_();
void stop_tracker_();
+ void restart_tracker_();
+ void toggle_tracker_();
- void ensure_tray();
+ [[nodiscard]] bool create_profile_from_preset(const QString& name);
+ void set_profile(const QString& new_name, bool migrate = true);
+ void set_profile_in_registry(const QString& profile);
+ void refresh_profile_list();
+ void die_on_profile_not_writable();
+ void maybe_start_profile_from_executable();
+ [[nodiscard]] static bool profile_name_from_dialog(QString& ret);
+ void copy_presets();
+ void create_empty_profile();
+ void create_copied_profile();
+ void open_profile_directory();
+
+ void ensure_tray();
void toggle_restore_from_tray(QSystemTrayIcon::ActivationReason e);
+ bool tray_enabled();
+ bool start_in_tray();
+
+ void save_modules();
+ bool module_tabs_enabled() const;
+
+ void exit(int status = EXIT_SUCCESS);
-signals:
- void start_tracker();
- void stop_tracker();
- void toggle_tracker();
- void restart_tracker();
public:
main_window();
- ~main_window();
- static void set_working_directory();
- bool maybe_die_on_config_not_writable(const QString& current, QStringList* ini_list);
- void die_on_config_not_writable();
- bool is_tray_enabled();
- bool start_in_tray();
+ ~main_window() override;
};
diff --git a/opentrack/main-window.ui b/opentrack/main-window.ui
new file mode 100644
index 00000000..32c9f57a
--- /dev/null
+++ b/opentrack/main-window.ui
@@ -0,0 +1,1343 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <author>Lovecraftian Octopus</author>
+ <class>main_window</class>
+ <widget class="QMainWindow" name="main_window">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>655</width>
+ <height>502</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>500</height>
+ </size>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../gui/opentrack-res.qrc">
+ <normaloff>:/images/opentrack.png</normaloff>:/images/opentrack.png</iconset>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">#video_feed { border: 0; }
+</string>
+ </property>
+ <widget class="QWidget" name="content">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QFrame" name="video_feed">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <widget class="QFrame" name="video_frame">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>320</width>
+ <height>240</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <widget class="QLabel" name="video_frame_label">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>320</width>
+ <height>240</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <family>Candara</family>
+ <pointsize>37</pointsize>
+ <weight>50</weight>
+ <bold>false</bold>
+ <kerning>true</kerning>
+ </font>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../gui/opentrack-res.qrc">:/images/tracking-not-started.png</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </widget>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="pose_widget" name="pose_display" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QGroupBox" name="box_raw_headpose">
+ <property name="minimumSize">
+ <size>
+ <width>316</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Raw tracker data</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_12">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="3">
+ <widget class="QLCDNumber" name="raw_yaw">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="lblZ_4">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="lblRotY_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QLCDNumber" name="raw_pitch">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lblY_4">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblX_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLCDNumber" name="raw_x">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="lblRotZ_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="lblRotX_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLCDNumber" name="raw_y">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QLCDNumber" name="raw_roll">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLCDNumber" name="raw_z">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QGroupBox" name="box_mapped_headpose">
+ <property name="minimumSize">
+ <size>
+ <width>316</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Game data</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_14">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="1" column="3">
+ <widget class="QLCDNumber" name="pose_pitch">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLCDNumber" name="pose_yaw">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLCDNumber" name="pose_z">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblx">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lblY_2">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLCDNumber" name="pose_x">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="lblRotY_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="lblRotZ_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="lblRotX_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="lblZ_2">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLCDNumber" name="pose_y">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QLCDNumber" name="pose_roll">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QWidget" name="groupControls" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>315</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="groupProfile" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>4</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>4</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolButton" name="profile_button">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="text">
+ <string>Profile</string>
+ </property>
+ <property name="popupMode">
+ <enum>QToolButton::InstantPopup</enum>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ <property name="arrowType">
+ <enum>Qt::DownArrow</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="iconcomboProfile">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>245</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="maxVisibleItems">
+ <number>20</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="groupOptions" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>4</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>4</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="btnShortcuts">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Options</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../gui/opentrack-res.qrc">
+ <normaloff>:/images/tools.png</normaloff>:/images/tools.png</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>80</width>
+ <height>24</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnEditCurves">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Mapping</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../gui/opentrack-res.qrc">
+ <normaloff>:/images/curves.png</normaloff>:/images/curves.png</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>80</width>
+ <height>24</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupStartStop">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Tracking</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>4</number>
+ </property>
+ <property name="topMargin">
+ <number>4</number>
+ </property>
+ <property name="rightMargin">
+ <number>4</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QToolButton" name="btnStartTracker">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>34</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Start</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="btnStopTracker">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>34</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Stop</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QWidget" name="modules" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>310</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>4</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupTracker">
+ <property name="title">
+ <string>Input</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="leftMargin">
+ <number>4</number>
+ </property>
+ <property name="topMargin">
+ <number>4</number>
+ </property>
+ <property name="rightMargin">
+ <number>4</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>6</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="iconcomboTrackerSource">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::TabFocus</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QToolButton" name="btnShowEngineControls">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <family>DejaVu Sans</family>
+ <stylestrategy>PreferAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ <property name="text">
+ <string>🔨</string>
+ </property>
+ <property name="flat" stdset="0">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupProto">
+ <property name="title">
+ <string>Output</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <property name="leftMargin">
+ <number>4</number>
+ </property>
+ <property name="topMargin">
+ <number>4</number>
+ </property>
+ <property name="rightMargin">
+ <number>4</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>6</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="iconcomboProtocol">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::TabFocus</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QToolButton" name="btnShowServerControls">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <family>DejaVu Sans</family>
+ <stylestrategy>PreferAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ <property name="text">
+ <string>🔨</string>
+ </property>
+ <property name="flat" stdset="0">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupFilter">
+ <property name="title">
+ <string>Filter</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <property name="leftMargin">
+ <number>4</number>
+ </property>
+ <property name="topMargin">
+ <number>4</number>
+ </property>
+ <property name="rightMargin">
+ <number>4</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>6</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="iconcomboFilter">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::TabFocus</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QToolButton" name="btnShowFilterControls">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <family>DejaVu Sans</family>
+ <stylestrategy>PreferAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ <property name="text">
+ <string>🔨</string>
+ </property>
+ <property name="flat" stdset="0">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>pose_widget</class>
+ <extends>QWidget</extends>
+ <header>pose-widget/pose-widget.hpp</header>
+ </customwidget>
+ </customwidgets>
+ <resources>
+ <include location="../gui/opentrack-res.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/variant/default/main.cpp b/opentrack/main.cpp
index 3ee93422..2c1dc607 100644
--- a/variant/default/main.cpp
+++ b/opentrack/main.cpp
@@ -5,12 +5,17 @@
# include <windows.h>
#endif
+#ifdef __clang__
+# pragma GCC diagnostic ignored "-Wmain"
+#endif
+
int main(int argc, char** argv)
{
- return run_application(argc, argv, []() { return new main_window; });
+ return run_application(argc, argv, [] { return std::make_unique<main_window>(); });
}
#if defined _MSC_VER
+
int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int /* nCmdShow */)
{
return main(__argc, __argv);
diff --git a/variant/default/new_config.ui b/opentrack/new_config.ui
index a262e725..05b8a14f 100644
--- a/variant/default/new_config.ui
+++ b/opentrack/new_config.ui
@@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>269</width>
- <height>67</height>
+ <width>437</width>
+ <height>110</height>
</rect>
</property>
<property name="windowTitle">
@@ -20,7 +20,7 @@
<iconset>
<normaloff>images/opentrack.png</normaloff>images/opentrack.png</iconset>
</property>
- <layout class="QFormLayout" name="formLayout">
+ <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
@@ -29,7 +29,14 @@
</widget>
</item>
<item row="0" column="1">
- <widget class="QLineEdit" name="lineEdit"/>
+ <widget class="QLineEdit" name="lineEdit">
+ <property name="minimumSize">
+ <size>
+ <width>271</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
</item>
<item row="1" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
diff --git a/variant/default/new_file_dialog.cpp b/opentrack/new_file_dialog.cpp
index 2da9ce56..70816c5d 100644
--- a/variant/default/new_file_dialog.cpp
+++ b/opentrack/new_file_dialog.cpp
@@ -23,7 +23,7 @@ void new_file_dialog::ok_clicked()
text = text.replace('\\', "");
if (text != "" && !text.endsWith(".ini"))
text += ".ini";
- if (text == "" || text == ".ini" || QFile(options::group::ini_directory() + "/" + text).exists())
+ if (text == "" || text == ".ini" || QFile(options::globals::ini_directory() + "/" + text).exists())
{
QMessageBox::warning(this,
tr("File exists"),
diff --git a/variant/default/new_file_dialog.h b/opentrack/new_file_dialog.h
index 5669e4a9..7244e524 100644
--- a/variant/default/new_file_dialog.h
+++ b/opentrack/new_file_dialog.h
@@ -11,7 +11,7 @@ class new_file_dialog : public QDialog
{
Q_OBJECT
public:
- new_file_dialog(QWidget* parent = 0);
+ new_file_dialog(QWidget* parent = nullptr);
bool is_ok(QString& name_);
private:
diff --git a/variant/default/opentrack.ico b/opentrack/opentrack.ico
index 5cac8da1..5cac8da1 100644
--- a/variant/default/opentrack.ico
+++ b/opentrack/opentrack.ico
Binary files differ
diff --git a/variant/default/resources.rc b/opentrack/resources.rc
index 6fd0253b..6fd0253b 100644
--- a/variant/default/resources.rc
+++ b/opentrack/resources.rc
diff --git a/options/base-value.cpp b/options/base-value.cpp
index 6c287321..950629d0 100644
--- a/options/base-value.cpp
+++ b/options/base-value.cpp
@@ -1,39 +1,33 @@
#include "base-value.hpp"
+#include <QThread>
using namespace options;
-base_value::base_value(bundle b, const QString& name, base_value::comparator cmp, std::type_index type_idx) :
- b(b),
- self_name(name),
- cmp(cmp),
- type_index(type_idx)
-{
- b->on_value_created(name, this);
-}
+//#define OTR_TRACE_NOTIFY
-base_value::~base_value()
-{
- b->on_value_destructed(self_name, this);
-}
+const bool value_::TRACE_NOTIFY =
+#ifdef OTR_TRACE_NOTIFY
+ true;
+#else
+ [] {
+ auto b = qgetenv("OTR_TRACE_NOTIFY");
+ return !b.isEmpty() && b != "0";
+ }();
+#endif
-void base_value::notify() const
+value_::value_(bundle const& b, const QString& name) noexcept :
+ b(b), self_name(name)
{
- bundle_value_changed();
+ b->on_value_created(this);
}
-void base_value::store(const QVariant& datum)
+value_::~value_()
{
- b->store_kv(self_name, datum);
+ b->on_value_destructed(this);
}
-namespace options {
-namespace detail {
-
-void set_base_value_to_default(base_value* val)
+void value_::maybe_trace(const char* str) const
{
- val->set_to_default();
+ if (TRACE_NOTIFY)
+ qDebug().noquote() << str << QThread::currentThreadId() << b->name() << self_name << get_variant();
}
-
-} // ns options::detail
-} // ns options
-
diff --git a/options/base-value.hpp b/options/base-value.hpp
index b094d693..5317191b 100644
--- a/options/base-value.hpp
+++ b/options/base-value.hpp
@@ -3,9 +3,12 @@
#include "bundle.hpp"
#include "slider.hpp"
#include "connector.hpp"
+#include "metatype.hpp"
#include "export.hpp"
-#include "compat/meta.hpp"
+#include "value-traits.hpp"
+
+#include <utility>
#include <QObject>
#include <QString>
@@ -13,92 +16,87 @@
#include <QPointF>
#include <QVariant>
-#include <typeindex>
-
-#define OPENTRACK_DEFINE_SLOT(t) void setValue(t datum) { store(datum); }
-#define OPENTRACK_DEFINE_SIGNAL(t) void valueChanged(t) const
+#define OTR_OPTIONS_SLOT(t) void setValue(t datum) noexcept { store_(datum); }
+#define OTR_OPTIONS_SIGNAL(t) void valueChanged(t) const
namespace options {
-class OTR_OPTIONS_EXPORT base_value : public QObject
+class OTR_OPTIONS_EXPORT value_ : public QObject
{
Q_OBJECT
- friend class detail::connector;
-
- using comparator = bool(*)(const QVariant& val1, const QVariant& val2);
+ template<typename t> using cv_qualified = detail::cv_qualified<t>;
template<typename t>
- using signal_sig = void(base_value::*)(cv_qualified<t>) const;
+ using signal_sig = void(value_::*)(cv_qualified<t>) const;
public:
- bundle get_bundle() { return b; }
QString name() const { return self_name; }
- base_value(bundle b, const QString& name, comparator cmp, std::type_index type_idx);
- ~base_value() override;
+ value_(bundle const& b, const QString& name) noexcept;
+ ~value_() override;
- // MSVC has ODR problems in 15.4
- // no C++17 "constexpr inline" for data declarations in MSVC
template<typename t>
- constexpr static auto value_changed()
+ static constexpr auto value_changed()
{
- return static_cast<signal_sig<t>>(&base_value::valueChanged);
+ return static_cast<signal_sig<t>>(&value_::valueChanged);
}
- void notify() const;
+ static const bool TRACE_NOTIFY;
signals:
- OPENTRACK_DEFINE_SIGNAL(double);
- OPENTRACK_DEFINE_SIGNAL(float);
- OPENTRACK_DEFINE_SIGNAL(int);
- OPENTRACK_DEFINE_SIGNAL(bool);
- OPENTRACK_DEFINE_SIGNAL(const QString&);
- OPENTRACK_DEFINE_SIGNAL(const slider_value&);
- OPENTRACK_DEFINE_SIGNAL(const QPointF&);
- OPENTRACK_DEFINE_SIGNAL(const QVariant&);
-
- OPENTRACK_DEFINE_SIGNAL(const QList<double>&);
- OPENTRACK_DEFINE_SIGNAL(const QList<float>&);
- OPENTRACK_DEFINE_SIGNAL(const QList<int>&);
- OPENTRACK_DEFINE_SIGNAL(const QList<bool>&);
- OPENTRACK_DEFINE_SIGNAL(const QList<QString>&);
- OPENTRACK_DEFINE_SIGNAL(const QList<slider_value>&);
- OPENTRACK_DEFINE_SIGNAL(const QList<QPointF>&);
+ OTR_OPTIONS_SIGNAL(double);
+ OTR_OPTIONS_SIGNAL(float);
+ OTR_OPTIONS_SIGNAL(int);
+ OTR_OPTIONS_SIGNAL(bool);
+ OTR_OPTIONS_SIGNAL(const QString&);
+ OTR_OPTIONS_SIGNAL(const slider_value&);
+ OTR_OPTIONS_SIGNAL(const QPointF&);
+ OTR_OPTIONS_SIGNAL(const QVariant&);
+
+ OTR_OPTIONS_SIGNAL(const QList<double>&);
+ OTR_OPTIONS_SIGNAL(const QList<float>&);
+ OTR_OPTIONS_SIGNAL(const QList<int>&);
+ OTR_OPTIONS_SIGNAL(const QList<bool>&);
+ OTR_OPTIONS_SIGNAL(const QList<QString>&);
+ OTR_OPTIONS_SIGNAL(const QList<slider_value>&);
+ OTR_OPTIONS_SIGNAL(const QList<QPointF>&);
+
protected:
bundle b;
QString self_name;
- comparator cmp;
- std::type_index type_index;
- void store(const QVariant& datum);
+ virtual void store_variant(QVariant&&) noexcept = 0;
+ virtual void store_variant(const QVariant&) noexcept = 0;
+
+ void maybe_trace(const char* str) const;
template<typename t>
- void store(const t& datum)
+ void store_(const t& datum)
{
- b->store_kv(self_name, QVariant::fromValue(datum));
+ using traits = detail::value_traits<t>;
+ store_variant(traits::qvariant_from_value(datum));
}
public slots:
- OPENTRACK_DEFINE_SLOT(double)
- OPENTRACK_DEFINE_SLOT(int)
- OPENTRACK_DEFINE_SLOT(bool)
- OPENTRACK_DEFINE_SLOT(const QString&)
- OPENTRACK_DEFINE_SLOT(const slider_value&)
- OPENTRACK_DEFINE_SLOT(const QPointF&)
- OPENTRACK_DEFINE_SLOT(const QVariant&)
-
- OPENTRACK_DEFINE_SLOT(const QList<double>&)
- OPENTRACK_DEFINE_SLOT(const QList<float>&)
- OPENTRACK_DEFINE_SLOT(const QList<int>&)
- OPENTRACK_DEFINE_SLOT(const QList<bool>&)
- OPENTRACK_DEFINE_SLOT(const QList<QString>&)
- OPENTRACK_DEFINE_SLOT(const QList<slider_value>&)
- OPENTRACK_DEFINE_SLOT(const QList<QPointF>&)
-
- virtual void reload() = 0;
- virtual void bundle_value_changed() const = 0;
- virtual void set_to_default() = 0;
-
- friend void ::options::detail::set_base_value_to_default(base_value* val);
+ OTR_OPTIONS_SLOT(double)
+ OTR_OPTIONS_SLOT(int)
+ OTR_OPTIONS_SLOT(bool)
+ OTR_OPTIONS_SLOT(const QString&)
+ OTR_OPTIONS_SLOT(const slider_value&)
+ OTR_OPTIONS_SLOT(const QPointF&)
+ OTR_OPTIONS_SLOT(const QVariant&)
+
+ OTR_OPTIONS_SLOT(const QList<double>&)
+ OTR_OPTIONS_SLOT(const QList<float>&)
+ OTR_OPTIONS_SLOT(const QList<int>&)
+ OTR_OPTIONS_SLOT(const QList<bool>&)
+ OTR_OPTIONS_SLOT(const QList<QString>&)
+ OTR_OPTIONS_SLOT(const QList<slider_value>&)
+ OTR_OPTIONS_SLOT(const QList<QPointF>&)
+
+ virtual void set_to_default() noexcept = 0;
+ virtual void notify() const = 0;
+ virtual void notify_() const = 0;
+ virtual QVariant get_variant() const noexcept = 0;
};
} //ns options
diff --git a/options/bundle.cpp b/options/bundle.cpp
index 4a133402..517005f0 100644
--- a/options/bundle.cpp
+++ b/options/bundle.cpp
@@ -8,133 +8,134 @@
#include "bundle.hpp"
#include "value.hpp"
+#include "globals.hpp"
-#include <QThread>
-#include <QApplication>
+#include <cstdlib>
-using options::base_value;
+#include <QThread>
+#include <QCoreApplication>
-namespace options
-{
+using namespace options;
+using namespace options::globals;
-namespace detail {
+namespace options::detail {
-bundle::bundle(const QString& group_name)
- : mtx(QMutex::Recursive),
+bundle::bundle(const QString& group_name) :
group_name(group_name),
saved(group_name),
transient(saved)
{
}
-bundle::~bundle()
+bundle::~bundle() = default;
+
+void bundle::reload_no_notify()
{
+ if (group_name.isEmpty())
+ return;
+
+ QMutexLocker l{&mtx};
+
+ saved = group(group_name);
+ transient = saved;
}
-void bundle::reload()
+void bundle::notify()
{
- if (group_name.size())
{
QMutexLocker l(&mtx);
- saved = group(group_name);
- const bool has_changes = is_modified();
- transient = saved;
+ connector::notify_all_values();
+ }
- if (has_changes)
- {
- connector::notify_all_values();
- emit reloading();
- emit changed();
- }
+ emit reloading();
+ emit changed();
+}
+
+void bundle::reload()
+{
+ {
+ QMutexLocker l{&mtx};
+ reload_no_notify();
+ connector::notify_all_values();
}
+ emit reloading();
+ emit changed();
}
void bundle::set_all_to_default()
{
- QMutexLocker l(&mtx);
-
- forall([](const QString&, base_value* val) { set_base_value_to_default(val); });
-
- if (is_modified())
- group::mark_ini_modified();
+ connector::set_all_to_default_();
}
-void bundle::store_kv(const QString& name, const QVariant& datum)
+void bundle::store_kv(const QString& name, QVariant&& value)
{
- QMutexLocker l(&mtx);
-
- transient.put(name, datum);
+ if (group_name.isEmpty())
+ return;
- if (group_name.size())
+ {
+ options::globals::detail::mark_ini_modified();
+ QMutexLocker l{&mtx};
+ transient.put(name, value);
connector::notify_values(name);
+ }
emit changed();
}
+void bundle::store_kv(const QString& name, const QVariant& value)
+{
+ if (group_name.isEmpty())
+ return;
+
+ store_kv(name, QVariant{value});
+}
+
+QVariant bundle::get_variant(const QString& name) const
+{
+ QMutexLocker l{&mtx};
+ return transient.get_variant(name);
+}
+
bool bundle::contains(const QString &name) const
{
- QMutexLocker l(mtx);
+ QMutexLocker l{&mtx};
return transient.contains(name);
}
void bundle::save()
{
- if (QThread::currentThread() != qApp->thread())
+ if (QThread::currentThread() != qApp->thread()) // NOLINT
qDebug() << "group::save - current thread not ui thread";
- if (group_name.size() == 0)
+ if (group_name.isEmpty())
return;
- bool modified_ = false;
-
{
- QMutexLocker l(&mtx);
-
- if (is_modified())
- {
- //qDebug() << "bundle" << group_name << "changed, saving";
- modified_ = true;
- saved = transient;
- saved.save();
- }
+ QMutexLocker l{&mtx};
+ saved = transient;
+ saved.save();
}
- if (modified_)
- {
- qDebug() << "saving" << name();
- emit saving();
- }
+ emit saving();
}
-bool bundle::is_modified() const
+void bundler::reload_no_notify_()
{
- QMutexLocker l(mtx);
-
- for (const auto& kv : transient.kvs)
- {
- const QVariant other = saved.get<QVariant>(kv.first);
- if (!saved.contains(kv.first) || !is_equal(kv.first, kv.second, other))
- {
- //if (logspam)
- // qDebug() << "bundle" << group_name << "modified" << "key" << kv.first << "-" << other << "<>" << kv.second;
- return true;
- }
- }
+ QMutexLocker l(&implsgl_mtx);
- for (const auto& kv : saved.kvs)
+ for (auto& kv : implsgl_data)
{
- if (!transient.contains(kv.first))
+ weak bundle = kv.second;
+ shared bundle_ = bundle.lock();
+ if (bundle_)
{
- //if (logspam)
- // qDebug() << "bundle" << group_name << "modified" << "key" << kv.first << "-" << other << "<>" << kv.second;
- return true;
+ //qDebug() << "bundle: reverting" << kv.first << "due to profile change";
+ bundle_->reload_no_notify();
}
}
-
- return false;
}
-void bundler::after_profile_changed_()
+void bundler::notify_()
{
QMutexLocker l(&implsgl_mtx);
@@ -145,30 +146,31 @@ void bundler::after_profile_changed_()
if (bundle_)
{
//qDebug() << "bundle: reverting" << kv.first << "due to profile change";
- bundle_->reload();
+ bundle_->notify();
}
}
}
-void bundler::refresh_all_bundles()
+void bundler::reload_()
{
- singleton().after_profile_changed_();
+ QMutexLocker l(&implsgl_mtx);
+ notify_();
+ reload_no_notify_();
}
-bundler::bundler() : implsgl_mtx(QMutex::Recursive)
-{
-}
+void bundler::notify() { singleton().notify_(); }
+void bundler::reload_no_notify() { singleton().reload_no_notify_(); }
+void bundler::reload() { singleton().reload_(); }
-bundler::~bundler()
-{
- //qDebug() << "exit: bundle singleton";
-}
+bundler::bundler() = default;
+bundler::~bundler() = default;
-std::shared_ptr<bundler::v> bundler::make_bundle(const bundler::k& key)
+std::shared_ptr<bundler::v> bundler::make_bundle_(const k& key)
{
QMutexLocker l(&implsgl_mtx);
- auto it = implsgl_data.find(key);
+ using iter = decltype(implsgl_data.cbegin());
+ const iter it = implsgl_data.find(key);
if (it != implsgl_data.end())
{
@@ -182,33 +184,36 @@ std::shared_ptr<bundler::v> bundler::make_bundle(const bundler::k& key)
auto shr = shared(new v(key), [this, key](v* ptr) {
QMutexLocker l(&implsgl_mtx);
- auto it = implsgl_data.find(key);
+ const iter it = implsgl_data.find(key);
if (it != implsgl_data.end())
- implsgl_data.erase(it);
+ (void)implsgl_data.erase(it);
else
- qDebug() << "ERROR: can't find self-bundle!";
+ {
+ qCritical() << "ERROR: can't find self-bundle!";
+ std::abort();
+ }
delete ptr;
});
implsgl_data[key] = weak(shr);
return shr;
}
-OTR_OPTIONS_EXPORT bundler& singleton()
+bundler& bundler::singleton()
{
static bundler ret;
return ret;
}
-QMutex* bundle::get_mtx() const { return mtx; }
+} // ns options::detail
-} // end options::detail
+namespace options {
-OTR_OPTIONS_EXPORT std::shared_ptr<bundle_> make_bundle(const QString& name)
+std::shared_ptr<bundle_> make_bundle(const QString& name)
{
- if (name.size())
- return detail::singleton().make_bundle(name);
+ if (!name.isEmpty())
+ return detail::bundler::singleton().make_bundle_(name);
else
return std::make_shared<bundle_>(QString());
}
-} // end options
+} // ns options
diff --git a/options/bundle.hpp b/options/bundle.hpp
index 9b7b7f02..158fcef9 100644
--- a/options/bundle.hpp
+++ b/options/bundle.hpp
@@ -10,10 +10,11 @@
#include "group.hpp"
#include "connector.hpp"
+#include "compat/qhash.hpp"
#include <memory>
#include <tuple>
-#include <map>
+#include <unordered_map>
#include <memory>
#include <vector>
@@ -21,93 +22,96 @@
#include <QString>
#include <QVariant>
#include <QMutex>
-#include <QMutexLocker>
#include <QDebug>
#include "export.hpp"
-namespace options {
-
-namespace detail {
+namespace options::detail {
+ class bundle;
+ struct bundler;
+} // ns options::detail
-void set_base_value_to_default(base_value* val);
+namespace options {
+ using bundle_ = detail::bundle;
+ using bundle = std::shared_ptr<bundle_>;
+ OTR_OPTIONS_EXPORT std::shared_ptr<detail::bundle> make_bundle(const QString& name);
+} // ns options
-struct bundler;
+namespace options::detail {
class OTR_OPTIONS_EXPORT bundle final : public QObject, public connector
{
Q_OBJECT
- class OTR_OPTIONS_EXPORT mutex final : public QMutex
- {
- public:
- mutex(QMutex::RecursionMode mode) : QMutex(mode) {}
- operator QMutex*() const { return const_cast<QMutex*>(static_cast<const QMutex*>(this)); }
- };
+ friend struct bundler;
-private:
- mutex mtx;
+ mutable QMutex mtx { QMutex::Recursive };
const QString group_name;
group saved;
group transient;
- bundle(const bundle&) = delete;
- bundle(bundle&&) = delete;
- bundle& operator=(bundle&&) = delete;
- bundle& operator=(const bundle&) = delete;
- QMutex* get_mtx() const override;
+ void reload_no_notify();
signals:
void reloading();
void saving() const;
void changed() const;
+
public:
- never_inline bundle(const QString& group_name);
- never_inline ~bundle() override;
+ bundle(const bundle&) = delete;
+ bundle& operator=(const bundle&) = delete;
+
+ QMutex* get_mtx() const override { return &mtx; }
QString name() const { return group_name; }
- never_inline void store_kv(const QString& name, const QVariant& datum);
- never_inline bool contains(const QString& name) const;
- never_inline bool is_modified() const;
-
- template<typename t>
- t get(const QString& name) const
- {
- QMutexLocker l(mtx);
- return transient.get<t>(name);
- }
+
+ explicit bundle(const QString& group_name);
+ ~bundle() override;
+
+ void store_kv(const QString& name, QVariant&& datum);
+ void store_kv(const QString& name, const QVariant& datum);
+ bool contains(const QString& name) const;
+
+ QVariant get_variant(const QString& name) const;
+ void notify();
+
public slots:
void save();
void reload();
void set_all_to_default();
};
-OTR_OPTIONS_EXPORT bundler& singleton();
-
-struct OTR_OPTIONS_EXPORT bundler
+struct OTR_OPTIONS_EXPORT bundler final
{
-public:
using k = QString;
using v = bundle;
using weak = std::weak_ptr<v>;
using shared = std::shared_ptr<v>;
+
+ static void notify();
+ static void reload_no_notify();
+ static void reload();
+
private:
- QMutex implsgl_mtx;
- std::map<k, weak> implsgl_data;
- void after_profile_changed_();
-public:
+ QMutex implsgl_mtx { QMutex::Recursive };
+ std::unordered_map<k, weak> implsgl_data {};
+
+ void notify_();
+ void reload_no_notify_();
+
+ void reload_();
+
+ friend OTR_OPTIONS_EXPORT
+ std::shared_ptr<v> options::make_bundle(const QString& name);
+
+ std::shared_ptr<v> make_bundle_(const k& key);
+ static bundler& singleton();
+
bundler();
~bundler();
- std::shared_ptr<v> make_bundle(const k& key);
- static void refresh_all_bundles();
};
-OTR_OPTIONS_EXPORT bundler& singleton();
-} // ns options::detail
+void set_value_to_default(value_* val);
-using bundle_ = detail::bundle;
-using bundle = std::shared_ptr<bundle_>;
-
-OTR_OPTIONS_EXPORT std::shared_ptr<bundle_> make_bundle(const QString& name);
+} // ns options::detail
-} // ns options
diff --git a/options/connector.cpp b/options/connector.cpp
index 6031d9ed..e86958f7 100644
--- a/options/connector.cpp
+++ b/options/connector.cpp
@@ -9,132 +9,85 @@
#include "connector.hpp"
#include "value.hpp"
-#include <utility>
+namespace options::detail {
-namespace options {
-namespace detail {
+connector::connector() = default;
+connector::~connector() = default;
-static bool generic_is_equal(const QVariant& val1, const QVariant& val2)
-{
- return val1 == val2;
-}
-
-connector::~connector() {}
-
-bool connector::is_equal(const QString& name, const QVariant& val1, const QVariant& val2) const
-{
- QMutexLocker l(get_mtx());
-
- auto it = connected_values.find(name);
-
- if (it != connected_values.cend() && std::get<0>((*it).second).size() != 0)
- return std::get<1>((*it).second)(val1, val2);
- else
- return generic_is_equal(val1, val2);
-}
-
-bool connector::on_value_destructed_impl(const QString& name, value_type val)
+void connector::on_value_destructed(value_type val)
{
+ const QString& name = val->name();
QMutexLocker l(get_mtx());
- const bool ok = progn(
- auto it = connected_values.find(name);
+ const auto it = connected_values.find(name);
- if (it != connected_values.end())
+ if (it != connected_values.end())
+ {
+ value_vec& values = it->second;
+ for (auto it = values.begin(); it != values.end(); )
{
- std::vector<value_type>& values = std::get<0>((*it).second);
- for (auto it = values.begin(); it != values.end(); it++)
- {
- if (*it == val)
- {
- values.erase(it);
- return true;
- }
- }
+ if (*it == val)
+ it = values.erase(it);
+ else
+ it++;
}
- return false;
- );
+ }
- return ok;
+ if (it != connected_values.end() && it->second.empty())
+ connected_values.erase(it);
}
-
-
-void connector::on_value_destructed(const QString& name, value_type val)
+void connector::on_value_created(value_type val)
{
- if (!name.size())
- return;
+ const QString& name = val->name();
- const bool ok = on_value_destructed_impl(name, val);
-
- if (!ok)
- qWarning() << "options/connector: value destructed without creating;"
- << "bundle"
- << val->b->name()
- << "value-name" << name
- << "value-ptr" << quintptr(val);
-}
-
-void connector::on_value_created(const QString& name, value_type val)
-{
- if (!name.size())
+ if (name.isEmpty())
return;
QMutexLocker l(get_mtx());
- int i = 1;
- while (on_value_destructed_impl(name, val))
- {
- qWarning() << "options/connector: value created twice;"
- << "cnt" << i++
- << "bundle" << val->b->name()
- << "value-name" << name
- << "value-ptr" << quintptr(val);
- }
-
auto it = connected_values.find(name);
if (it != connected_values.end())
{
- tt& tmp = (*it).second;
- std::type_index& typeidx = std::get<2>(tmp);
- std::vector<value_type>& values = std::get<0>(tmp);
-
- if (typeidx != val->type_index)
- std::get<1>((*it).second) = generic_is_equal;
+ value_vec& values = it->second;
values.push_back(val);
}
else
{
- std::vector<value_type> vec;
+ value_vec vec;
+ vec.reserve(4);
vec.push_back(val);
- connected_values.emplace(name, tt(vec, val->cmp, val->type_index));
+ connected_values.emplace(name, vec);
}
}
void connector::notify_values(const QString& name) const
{
+ QMutexLocker l(get_mtx());
+
auto it = connected_values.find(name);
if (it != connected_values.cend())
- {
- for (value_type val : std::get<0>((*it).second))
- {
- val->bundle_value_changed();
- }
- }
+ for (value_type val : it->second)
+ val->notify();
}
void connector::notify_all_values() const
{
- for (auto& pair : connected_values)
- for (value_type val : std::get<0>(pair.second))
- val->bundle_value_changed();
+ QMutexLocker l(get_mtx());
+
+ for (const auto& [k, v] : connected_values)
+ for (value_type val : v)
+ val->notify();
}
-connector::connector()
+void connector::set_all_to_default_()
{
-}
+ QMutexLocker l(get_mtx());
+ for (auto& pair : connected_values)
+ for (auto& val : pair.second)
+ val->set_to_default();
}
-}
+} // ns options::detail
diff --git a/options/connector.hpp b/options/connector.hpp
index 5091ae67..025efda2 100644
--- a/options/connector.hpp
+++ b/options/connector.hpp
@@ -8,64 +8,42 @@
#pragma once
-#include <map>
+#include "compat/qhash.hpp"
+
+#include <unordered_map>
#include <vector>
-#include <tuple>
-#include <typeinfo>
-#include <typeindex>
-#include <QVariant>
+
#include <QString>
#include <QMutex>
-#include <QMutexLocker>
#include "export.hpp"
namespace options {
+ class value_;
+}
-class base_value;
-
-namespace detail {
+namespace options::detail {
class OTR_OPTIONS_EXPORT connector
{
- friend class ::options::base_value;
+ friend class ::options::value_;
- using value_type = base_value*;
+ using value_type = value_*;
using value_vec = std::vector<value_type>;
- using comparator = bool(*)(const QVariant&, const QVariant&);
- using tt = std::tuple<value_vec, comparator, std::type_index>;
- std::map<QString, tt> connected_values;
+ std::unordered_map<QString, value_vec> connected_values;
- void on_value_destructed(const QString& name, value_type val);
- void on_value_created(const QString& name, value_type val);
- bool on_value_destructed_impl(const QString& name, value_type val);
+ void on_value_destructed(value_type val);
+ void on_value_created(value_type val);
protected:
void notify_values(const QString& name) const;
void notify_all_values() const;
virtual QMutex* get_mtx() const = 0;
-
- template<typename F>
- void forall(F&& fun)
- {
- QMutexLocker l(get_mtx());
-
- for (auto& pair : connected_values)
- for (auto& val : std::get<0>(pair.second))
- fun(pair.first, val);
- }
+ void set_all_to_default_();
public:
connector();
virtual ~connector();
-
- bool is_equal(const QString& name, const QVariant& val1, const QVariant& val2) const;
-
- connector(const connector&) = default;
- connector& operator=(const connector&) = default;
- connector(connector&&) = default;
- connector& operator=(connector&&) = default;
};
-} // ns detail
-} // ns options
+} // ns options::detail
diff --git a/options/defs.hpp b/options/defs.hpp
index f093a22a..797a8fda 100644
--- a/options/defs.hpp
+++ b/options/defs.hpp
@@ -1,7 +1,4 @@
#pragma once
-#include <QString>
-
-#define OPENTRACK_CONFIG_FILENAME_KEY "settings-filename"
-#define OPENTRACK_DEFAULT_CONFIG "default.ini"
-#define OPENTRACK_DEFAULT_CONFIG_Q QStringLiteral("default.ini")
+#define OPENTRACK_PROFILE_FILENAME_KEY "settings-filename"
+#define OPENTRACK_DEFAULT_PROFILE "default.ini"
diff --git a/options/globals.cpp b/options/globals.cpp
new file mode 100644
index 00000000..39eb6014
--- /dev/null
+++ b/options/globals.cpp
@@ -0,0 +1,195 @@
+#include "globals.hpp"
+#include "compat/base-path.hpp"
+#include "defs.hpp"
+#include "opentrack-org.hxx"
+
+#include <QFile>
+#include <QFileInfo>
+#include <QDir>
+#include <QStandardPaths>
+#include <QDateTime>
+#include <QDebug>
+
+namespace options::globals::detail {
+
+ini_ctx::ini_ctx() = default;
+
+static bool is_portable_installation()
+{
+#if defined _WIN32
+ // must keep consistent between invocations
+ static const bool ret = QFile::exists(OPENTRACK_BASE_PATH + "/portable.txt");
+ return ret;
+#else
+ return false;
+#endif
+}
+
+saver_::~saver_()
+{
+ if (--ctx.refcount == 0 && ctx.modifiedp)
+ {
+ auto& settings = *ctx.qsettings;
+ settings.sync();
+ if (settings.status() != QSettings::NoError)
+ qDebug() << "error with .ini file" << settings.fileName() << settings.status();
+ ctx.modifiedp = false;
+ }
+ ctx.mtx.unlock();
+}
+
+saver_::saver_(ini_ctx& ini) : ctx { ini }
+{
+ ctx.refcount++;
+}
+
+ini_ctx& cur_settings()
+{
+ static ini_ctx ini;
+ const QString filename = ini_filename();
+
+ ini.mtx.lock();
+
+ if (ini.pathname != filename)
+ {
+ ini.qsettings.emplace(ini_combine(filename), QSettings::IniFormat);
+ ini.pathname = filename;
+ }
+
+ return ini;
+}
+
+ini_ctx& global_settings()
+{
+ static ini_ctx& ret = progn(
+ static ini_ctx ini;
+
+ if (!is_portable_installation())
+ // Windows registry or xdg on Linux
+ ini.qsettings.emplace(OPENTRACK_ORG);
+ else
+ // file in executable's directory
+ ini.qsettings.emplace(OPENTRACK_BASE_PATH + QStringLiteral("/globals.ini"),
+ QSettings::IniFormat);
+
+ ini.pathname = QStringLiteral(".");
+ return (ini_ctx&)ini;
+ );
+ ret.mtx.lock();
+ return ret;
+}
+
+void mark_ini_modified(bool value)
+{
+ auto& ini = cur_settings();
+ ini.modifiedp = value;
+ ini.mtx.unlock();
+}
+
+} // ns options::globals::detail
+
+namespace options::globals
+{
+
+using namespace detail;
+
+bool is_ini_modified()
+{
+ ini_ctx& ini = cur_settings();
+ bool ret = ini.modifiedp;
+ ini.mtx.unlock();
+ return ret;
+}
+
+QString ini_filename()
+{
+ return with_global_settings_object([&](QSettings& settings) {
+ static_assert(sizeof(OPENTRACK_DEFAULT_PROFILE) > 1);
+ static_assert(sizeof(OPENTRACK_PROFILE_FILENAME_KEY) > 1);
+
+ const QString ret = settings.value(QStringLiteral(OPENTRACK_PROFILE_FILENAME_KEY),
+ QStringLiteral(OPENTRACK_DEFAULT_PROFILE)).toString();
+ if (ret.isEmpty())
+ return QStringLiteral(OPENTRACK_DEFAULT_PROFILE);
+ return ret;
+ });
+}
+
+QString ini_pathname()
+{
+ return ini_combine(ini_filename());
+}
+
+QString ini_combine(const QString& filename)
+{
+ return QStringLiteral("%1/%2").arg(ini_directory(), filename);
+}
+
+QStringList ini_list()
+{
+ static QMutex mtx;
+ static QStringList list;
+ QMutexLocker l{&mtx};
+
+ const QString dirname = ini_directory();
+
+ {
+ static QDateTime last_time = {};
+ auto time = QFileInfo{dirname}.lastModified();
+ if (time == last_time)
+ return list;
+ last_time = time;
+ }
+
+ QDir settings_dir(dirname);
+
+ using f = QDir::Filter;
+ list = settings_dir.entryList({ QStringLiteral("*.ini") }, f::Files | f::Readable, QDir::Name);
+ std::sort(list.begin(), list.end());
+ return list;
+}
+
+void mark_global_ini_modified(bool value)
+{
+ auto& ini = global_settings();
+ ini.modifiedp = value;
+ ini.mtx.unlock();
+}
+
+static QString ini_directory_()
+{
+ if (detail::is_portable_installation())
+ {
+fail: constexpr const char* subdir = "ini";
+ QString dir = OPENTRACK_BASE_PATH;
+ if (dir.isEmpty())
+ dir = '.';
+ (void)QDir(dir).mkpath(subdir);
+ return QStringLiteral("%1/%2").arg(dir, subdir);
+ }
+ else
+ {
+ QString dir = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).value(0, QString());
+ if (dir.isEmpty())
+ goto fail;
+ const QString fmt = QStringLiteral("%1/%2");
+#if !defined _WIN32 && !defined __APPLE__
+ if (!QFile::exists(fmt.arg(dir, OPENTRACK_ORG)))
+ {
+ dir = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).value(0, QString());
+ if (dir.isEmpty())
+ goto fail;
+ }
+#endif
+ (void)QDir(dir).mkpath(OPENTRACK_ORG);
+ return fmt.arg(dir, OPENTRACK_ORG);
+ }
+}
+
+QString ini_directory()
+{
+ static const QString dir = ini_directory_();
+ return dir;
+}
+
+} // ns options::globals
diff --git a/options/globals.hpp b/options/globals.hpp
new file mode 100644
index 00000000..af242dc9
--- /dev/null
+++ b/options/globals.hpp
@@ -0,0 +1,74 @@
+#pragma once
+
+#include "export.hpp"
+#include "compat/macros.h"
+
+#include <optional>
+
+#include <QString>
+#include <QSettings>
+#include <QMutex>
+
+namespace options::globals::detail {
+
+struct saver_;
+
+struct OTR_OPTIONS_EXPORT ini_ctx
+{
+ std::optional<QSettings> qsettings { std::in_place };
+ QString pathname;
+ QMutex mtx { QMutex::Recursive };
+
+ unsigned refcount = 0;
+ bool modifiedp = false;
+
+ ini_ctx();
+};
+
+struct OTR_OPTIONS_EXPORT saver_ final
+{
+ ini_ctx& ctx;
+
+ never_inline ~saver_();
+ explicit never_inline saver_(ini_ctx& ini);
+};
+
+template<typename F>
+never_inline
+auto with_settings_object_(ini_ctx& ini, F&& fun)
+{
+ saver_ saver { ini };
+
+ return fun(*ini.qsettings);
+}
+
+OTR_OPTIONS_EXPORT ini_ctx& cur_settings();
+OTR_OPTIONS_EXPORT ini_ctx& global_settings();
+
+OTR_OPTIONS_EXPORT void mark_ini_modified(bool value = true);
+
+} // ns options::globals::detail
+
+namespace options::globals
+{
+ OTR_OPTIONS_EXPORT void mark_global_ini_modified(bool value = true);
+ OTR_OPTIONS_EXPORT QString ini_directory();
+ OTR_OPTIONS_EXPORT QString ini_filename();
+ OTR_OPTIONS_EXPORT QString ini_pathname();
+ OTR_OPTIONS_EXPORT QString ini_combine(const QString& filename);
+ OTR_OPTIONS_EXPORT QStringList ini_list();
+
+ template<typename F>
+ auto with_settings_object(F&& fun)
+ {
+ using namespace detail;
+ return with_settings_object_(cur_settings(), fun);
+ }
+
+ template<typename F>
+ auto with_global_settings_object(F&& fun)
+ {
+ using namespace detail;
+ return with_settings_object_(global_settings(), fun);
+ }
+} // ns options::globals
diff --git a/options/group.cpp b/options/group.cpp
index 69378d51..d5829008 100644
--- a/options/group.cpp
+++ b/options/group.cpp
@@ -8,205 +8,75 @@
#include "group.hpp"
#include "defs.hpp"
+#include "globals.hpp"
-#include "compat/timer.hpp"
-#include "compat/library-path.hpp"
-
-#include <cmath>
+#include <utility>
+#include <algorithm>
#include <QFile>
-#include <QStandardPaths>
#include <QDir>
-#include <QDebug>
-namespace options {
+namespace options::detail {
+
+using namespace options::globals;
group::group(const QString& name) : name(name)
{
- if (name == "")
+ constexpr unsigned reserved_buckets = 64;
+ kvs.reserve(reserved_buckets);
+ kvs.max_load_factor(0.4375);
+
+ if (name.isEmpty())
return;
with_settings_object([&](QSettings& conf) {
conf.beginGroup(name);
- for (auto& k_ : conf.childKeys())
- {
- auto tmp = k_.toUtf8();
- QString k(tmp);
- QVariant val = conf.value(k_);
- if (val.type() != QVariant::Invalid)
- kvs[k] = std::move(val);
- }
+ for (auto const& k : conf.childKeys())
+ kvs[k] = conf.value(k);
conf.endGroup();
});
}
void group::save() const
{
- if (name == "")
+ if (name.isEmpty())
return;
with_settings_object([&](QSettings& s) {
s.beginGroup(name);
- for (auto& i : kvs)
+ for (auto const& i : kvs)
s.setValue(i.first, i.second);
s.endGroup();
-
- mark_ini_modified();
});
}
-void group::put(const QString &s, const QVariant &d)
-{
- kvs[s] = d;
-}
-
-bool group::contains(const QString &s) const
-{
- const auto it = kvs.find(s);
- return it != kvs.cend() && it->second != QVariant::Invalid;
-}
-
-bool group::is_portable_installation()
+void group::put(const QString& s, QVariant&& d)
{
-#if defined _WIN32
- if (QFile::exists(OPENTRACK_BASE_PATH + "/portable.txt"))
- return true;
-#endif
- return false;
-}
-
-QString group::ini_directory()
-{
-
- QString dir;
-
- if (is_portable_installation())
- {
- dir = OPENTRACK_BASE_PATH;
-
- static const QString subdir = "ini";
-
- if (!QDir(dir).mkpath(subdir))
- return QString();
-
- return dir + '/' + subdir;
- }
+ if (d.isNull())
+ kvs.erase(s);
else
- {
- dir = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).value(0, QString());
- if (dir.isEmpty())
- return QString();
- if (!QDir(dir).mkpath(OPENTRACK_ORG))
- return QString();
-
- dir += '/';
- dir += OPENTRACK_ORG;
- }
-
- return dir;
-}
-
-QString group::ini_filename()
-{
- return with_global_settings_object([&](QSettings& settings) {
- const QString ret = settings.value(OPENTRACK_CONFIG_FILENAME_KEY, OPENTRACK_DEFAULT_CONFIG).toString();
- if (ret.size() == 0)
- return QStringLiteral(OPENTRACK_DEFAULT_CONFIG);
- return ret;
- });
+ kvs[s] = d;
}
-QString group::ini_pathname()
+void group::put(const QString& s, const QVariant& d)
{
- const auto dir = ini_directory();
- if (dir == "")
- return "";
- return dir + "/" + ini_filename();
+ put(s, QVariant{d});
}
-QString group::ini_combine(const QString& filename)
+bool group::contains(const QString& s) const
{
- return ini_directory() + QStringLiteral("/") + filename;
-}
-
-QStringList group::ini_list()
-{
- const auto dirname = ini_directory();
- if (dirname == "")
- return QStringList();
- QDir settings_dir(dirname);
- QStringList list = settings_dir.entryList( QStringList { "*.ini" } , QDir::Files, QDir::Name );
- std::sort(list.begin(), list.end());
- return list;
-}
-
-void group::mark_ini_modified()
-{
- QMutexLocker l(&cur_ini_mtx);
- ini_modifiedp = true;
-}
-
-QString group::cur_ini_pathname;
-
-std::shared_ptr<QSettings> group::cur_ini;
-QMutex group::cur_ini_mtx(QMutex::Recursive);
-int group::ini_refcount = 0;
-bool group::ini_modifiedp = false;
-
-std::shared_ptr<QSettings> group::cur_global_ini;
-QMutex group::global_ini_mtx(QMutex::Recursive);
-int group::global_ini_refcount = 0;
-bool group::global_ini_modifiedp = false;
-
-std::shared_ptr<QSettings> group::cur_settings_object()
-{
- const QString pathname = ini_pathname();
-
- if (pathname.isEmpty())
- return std::make_shared<QSettings>();
-
- if (pathname != cur_ini_pathname)
- {
- cur_ini = std::make_shared<QSettings>(pathname, QSettings::IniFormat);
- cur_ini_pathname = pathname;
- }
-
- return cur_ini;
+ const auto it = kvs.find(s);
+ return it != kvs.cend();
}
-std::shared_ptr<QSettings> group::cur_global_settings_object()
+QVariant group::get_variant(const QString& name) const
{
- if (cur_global_ini)
- return cur_global_ini;
-
- if (!is_portable_installation())
- cur_global_ini = std::make_shared<QSettings>(OPENTRACK_ORG);
- else
- {
- static const QString pathname = OPENTRACK_BASE_PATH + QStringLiteral("/globals.ini");
- cur_global_ini = std::make_shared<QSettings>(pathname, QSettings::IniFormat);
- }
+ auto it = kvs.find(name);
+ if (it != kvs.cend())
+ return it->second;
- return cur_global_ini;
+ return {};
}
-never_inline
-group::saver_::~saver_()
-{
- if (--refcount == 0 && modifiedp)
- {
- modifiedp = false;
- s.sync();
- if (s.status() != QSettings::NoError)
- qDebug() << "error with .ini file" << s.fileName() << s.status();
- }
-}
-
-never_inline
-group::saver_::saver_(QSettings& s, int& refcount, bool& modifiedp) :
- s(s), refcount(refcount), modifiedp(modifiedp)
-{
- refcount++;
-}
+} // ns options::detail
-} // ns options
diff --git a/options/group.hpp b/options/group.hpp
index c933b134..11bab965 100644
--- a/options/group.hpp
+++ b/options/group.hpp
@@ -1,93 +1,39 @@
#pragma once
+#include "options/defs.hpp"
+
+#include "compat/base-path.hpp"
+#include "compat/library-path.hpp"
+#include "compat/macros.h"
+#include "compat/qhash.hpp"
#include "export.hpp"
-// included here to propagate into callers of options::group
-#include "metatype.hpp"
+#include <optional>
+#include <unordered_map>
-#include <map>
-#include <memory>
#include <QString>
-#include <QList>
#include <QVariant>
-#include <QSettings>
-#include <QMutex>
-
-namespace options {
-
-// snapshot of qsettings group at given time
-class OTR_OPTIONS_EXPORT group final
-{
- QString name;
-
- static QString cur_ini_pathname;
-
- static std::shared_ptr<QSettings> cur_ini;
- static int ini_refcount;
- static bool ini_modifiedp;
- static QMutex cur_ini_mtx;
-
- static std::shared_ptr<QSettings> cur_global_ini;
- static int global_ini_refcount;
- static bool global_ini_modifiedp;
- static QMutex global_ini_mtx;
-
- struct OTR_OPTIONS_EXPORT saver_ final
- {
- QSettings& s;
- int& refcount;
- bool& modifiedp;
-
- never_inline ~saver_();
- never_inline saver_(QSettings& s, int& refcount, bool& modifiedp);
- };
- static std::shared_ptr<QSettings> cur_settings_object();
- static std::shared_ptr<QSettings> cur_global_settings_object();
-public:
- std::map<QString, QVariant> kvs;
- group(const QString& name);
- void save() const;
- void put(const QString& s, const QVariant& d);
- bool contains(const QString& s) const;
- static QString ini_directory();
- static QString ini_filename();
- static QString ini_pathname();
- static QString ini_combine(const QString& filename);
- static QStringList ini_list();
- static bool is_portable_installation();
+#include <QDebug>
- static void mark_ini_modified();
+// XXX TODO remove qsettings usage -sh 20180624
- template<typename t>
- never_inline
- t get(const QString& k) const
+namespace options::detail {
+ // snapshot of qsettings group at given time
+ class OTR_OPTIONS_EXPORT group final
{
- auto value = kvs.find(k);
- if (value != kvs.cend())
- return value->second.value<t>();
- return t();
- }
+ QString name;
- template<typename F>
- never_inline
- static auto with_settings_object(F&& fun)
- {
- QMutexLocker l(&cur_ini_mtx);
- saver_ saver { *cur_settings_object(), ini_refcount, ini_modifiedp };
+ public:
+ std::unordered_map<QString, QVariant> kvs;
+ explicit group(const QString& name);
+ void save() const;
+ void put(const QString& s, const QVariant& d);
+ void put(const QString& s, QVariant&& d);
+ bool contains(const QString& s) const;
- return fun(saver.s);
- }
-
- template<typename F>
- static auto with_global_settings_object(F&& fun)
- {
- QMutexLocker l(&global_ini_mtx);
- saver_ saver { *cur_global_settings_object(), global_ini_refcount, global_ini_modifiedp };
- global_ini_modifiedp = true;
+ never_inline QVariant get_variant(const QString& name) const;
+ };
+} // ns options::detail
- return fun(saver.s);
- }
-};
-}
diff --git a/options/lang/de_DE.ts b/options/lang/de_DE.ts
new file mode 100644
index 00000000..1552582e
--- /dev/null
+++ b/options/lang/de_DE.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+</TS>
diff --git a/options/lang/zh_CN.ts b/options/lang/zh_CN.ts
index 6401616d..e5ca8aa9 100644
--- a/options/lang/zh_CN.ts
+++ b/options/lang/zh_CN.ts
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
</TS>
diff --git a/options/metatype.cpp b/options/metatype.cpp
index ed12821b..7430c00a 100644
--- a/options/metatype.cpp
+++ b/options/metatype.cpp
@@ -1,22 +1,26 @@
-#include "metatype.hpp"
-
-#define OPENTRACK_REGISTER_METATYPE(t) ::options::detail::custom_type_initializer::declare_for_type<t>(#t)
+#include <QMetaType>
-namespace options {
-namespace detail {
+namespace options::detail {
-custom_type_initializer::custom_type_initializer()
+template<typename t>
+void declare_metatype_for_type(const char* str)
{
- OPENTRACK_REGISTER_METATYPE(options::slider_value);
- OPENTRACK_REGISTER_METATYPE(QList<double>);
- OPENTRACK_REGISTER_METATYPE(QList<float>);
- OPENTRACK_REGISTER_METATYPE(QList<int>);
- OPENTRACK_REGISTER_METATYPE(QList<bool>);
- OPENTRACK_REGISTER_METATYPE(QList<QString>);
- OPENTRACK_REGISTER_METATYPE(QList<QPointF>);
+ qRegisterMetaType<t>(str);
+ qRegisterMetaTypeStreamOperators<t>();
}
-const custom_type_initializer custom_type_initializer::singleton;
+} // ns options::detail
-}
-}
+#define OPENTRACK_DEFINE_METATYPE3(t, ctr) \
+ static \
+ const char init_##ctr = /* NOLINT(misc-definitions-in-headers) */ \
+ (::options::detail::declare_metatype_for_type<t>(#t), 0); \
+
+#define OPENTRACK_DEFINE_METATYPE2(t, ctr) \
+ OPENTRACK_DEFINE_METATYPE3(t, ctr)
+
+#define OPENTRACK_DEFINE_METATYPE(t) \
+ OPENTRACK_DEFINE_METATYPE2(t, __COUNTER__)
+
+#define OPENTRACK_METATYPE_(x) OPENTRACK_DEFINE_METATYPE(x)
+#include "metatype.hpp"
diff --git a/options/metatype.hpp b/options/metatype.hpp
index c889766a..ca0947fb 100644
--- a/options/metatype.hpp
+++ b/options/metatype.hpp
@@ -1,38 +1,25 @@
#pragma once
-#include <QMetaType>
+#include "slider.hpp"
+#include "defs.hpp"
+
#include <QList>
#include <QString>
#include <QPointF>
-#include <QDataStream>
#include <QDebug>
-#include "export.hpp"
-#include "slider.hpp"
-
-Q_DECLARE_METATYPE(QList<double>)
-Q_DECLARE_METATYPE(QList<float>)
-Q_DECLARE_METATYPE(QList<int>)
-Q_DECLARE_METATYPE(QList<bool>)
-Q_DECLARE_METATYPE(QList<QString>)
-Q_DECLARE_METATYPE(QList<QPointF>)
-Q_DECLARE_METATYPE(::options::slider_value)
-
-namespace options {
-namespace detail {
-
-struct custom_type_initializer final
-{
- static const custom_type_initializer singleton;
-
- custom_type_initializer();
-
- template<typename t> static void declare_for_type(const char* str)
- {
- qRegisterMetaType<t>(str);
- qRegisterMetaTypeStreamOperators<t>();
- }
-};
+#include <QMetaType>
-}
-}
+#if !defined OPENTRACK_METATYPE_
+# define OPENTRACK_METATYPE(x) Q_DECLARE_METATYPE(x)
+#else
+# define OPENTRACK_METATYPE(x) Q_DECLARE_METATYPE(x) OPENTRACK_METATYPE_(x)
+#endif
+
+OPENTRACK_METATYPE(QList<double>)
+OPENTRACK_METATYPE(QList<float>)
+OPENTRACK_METATYPE(QList<int>)
+OPENTRACK_METATYPE(QList<bool>)
+OPENTRACK_METATYPE(QList<QString>)
+OPENTRACK_METATYPE(QList<QPointF>)
+OPENTRACK_METATYPE(::options::slider_value)
diff --git a/options/options.hpp b/options/options.hpp
index bc892120..a99d5c59 100644
--- a/options/options.hpp
+++ b/options/options.hpp
@@ -8,9 +8,8 @@
#include "defs.hpp"
#include "bundle.hpp"
-#include "group.hpp"
#include "slider.hpp"
#include "value.hpp"
#include "tie.hpp"
-#include "metatype.hpp"
#include "scoped.hpp"
+#include "globals.hpp"
diff --git a/options/scoped.cpp b/options/scoped.cpp
index 2974dfdb..33db7907 100644
--- a/options/scoped.cpp
+++ b/options/scoped.cpp
@@ -1,4 +1,5 @@
#include "scoped.hpp"
+#include "compat/run-in-thread.hpp"
#include <QApplication>
#include <QThread>
@@ -10,27 +11,27 @@
namespace options {
// XXX hack: the flag shouldn't be here as action at distance -sh 20160926
-static std::atomic_bool teardown_flag(false);
-static void set_teardown_flag(bool value);
+static std::atomic<bool> teardown_flag = false;
+[[nodiscard]] static bool set_teardown_flag(bool value);
static void ensure_thread();
static bool is_tracker_teardown();
-static void set_teardown_flag(bool value)
+static bool set_teardown_flag(bool value)
{
// flag being set means "opts" is about to go out of scope due to tracker stop
// in this case we don't reload options. we only want to reload when cancel is pressed.
ensure_thread();
- teardown_flag = value;
+ return teardown_flag.exchange(value);
}
static void ensure_thread()
{
// only as a bug check
- if (qApp == nullptr)
+ if (qApp == nullptr) // NOLINT
abort();
- const QThread* ui_thread = qApp->thread();
+ const QThread* ui_thread = qApp->thread(); // NOLINT
const QThread* curthread = QThread::currentThread();
if (ui_thread == nullptr)
@@ -47,8 +48,14 @@ static bool is_tracker_teardown()
opts::~opts()
{
- if (!is_tracker_teardown())
+ // XXX TODO debug crash with ps3eye dialog in pt tracker -sh 20211019
+ if (!is_tracker_teardown() && raii)
+#if 1
+ run_in_thread_sync(qApp->thread(), [this]{ b->reload(); });
+#else
+ assert(b);
b->reload();
+#endif
#if 0
else
qDebug() << "in teardown, not reloading" << b->name();
@@ -59,14 +66,13 @@ opts::opts(const QString &name) : b(make_bundle(name))
{
}
-with_tracker_teardown::with_tracker_teardown() : old_value(teardown_flag)
+with_tracker_teardown::with_tracker_teardown() : old_value{set_teardown_flag(true)}
{
- set_teardown_flag(true);
}
with_tracker_teardown::~with_tracker_teardown()
{
- set_teardown_flag(old_value);
+ (void)set_teardown_flag(old_value);
}
} // ns options
diff --git a/options/scoped.hpp b/options/scoped.hpp
index 49c3d18f..81e6bd19 100644
--- a/options/scoped.hpp
+++ b/options/scoped.hpp
@@ -6,8 +6,6 @@
#include "export.hpp"
-#include <atomic>
-
namespace options {
struct OTR_OPTIONS_EXPORT with_tracker_teardown final
@@ -26,11 +24,16 @@ struct OTR_OPTIONS_EXPORT opts
bundle b;
- virtual ~opts();
- opts(const QString& name);
-
opts& operator=(const opts&) = delete;
opts(const opts&) = delete;
+
+ void set_raii_dtor_state(bool x) { raii = x; }
+
+protected:
+ explicit opts(const QString& name);
+ ~opts();
+private:
+ bool raii = true;
};
}
diff --git a/options/slider.cpp b/options/slider.cpp
index 33000ee9..76d1dddc 100644
--- a/options/slider.cpp
+++ b/options/slider.cpp
@@ -24,39 +24,24 @@ slider_value::slider_value(double cur, double min, double max) :
cur_ = min_;
}
-slider_value::slider_value(const slider_value& v) : slider_value(v.cur(), v.min(), v.max())
-{
-}
-
-slider_value::slider_value() : slider_value(0, 0, 0)
-{
-}
-
-slider_value& slider_value::operator=(const slider_value& v)
-{
- cur_ = v.cur_;
-
- min_ = v.min_;
- max_ = v.max_;
-
- return *this;
-}
-
bool slider_value::operator==(const slider_value& v) const
{
- using std::fabs;
-
constexpr double eps = 2e-3;
#if 1
- return (fabs(v.cur_ - cur_) < eps &&
- fabs(v.min_ - min_) < eps &&
- fabs(v.max_ - max_) < eps);
+ return (std::fabs(v.cur_ - cur_) < eps &&
+ std::fabs(v.min_ - min_) < eps &&
+ std::fabs(v.max_ - max_) < eps);
#else
- return (fabs(v.cur_ - cur_) < eps);
+ return (std::fabs(v.cur_ - cur_) < eps);
#endif
}
+bool slider_value::operator!=(const slider_value& v) const
+{
+ return !(*this == v);
+}
+
slider_value slider_value::update_from_slider(int pos, int q_min, int q_max) const
{
slider_value v(*this);
@@ -67,11 +52,11 @@ slider_value slider_value::update_from_slider(int pos, int q_min, int q_max) con
: (((pos - q_min) * (v.max() - v.min())) / q_diff + v.min());
if (sv_pos < v.min())
- v = slider_value(v.min(), v.min(), v.max());
+ v = { v.min(), v.min(), v.max() };
else if (sv_pos > v.max())
- v = slider_value(v.max(), v.min(), v.max());
+ v = { v.max(), v.min(), v.max() };
else
- v = slider_value(sv_pos, v.min(), v.max());
+ v = { sv_pos, v.min(), v.max() };
return v;
}
@@ -86,11 +71,11 @@ int slider_value::to_slider_pos(int q_min, int q_max) const
return int(std::round(((cur() - min()) * q_diff / div) + q_min));
}
-} // end ns options
+} // ns options
-QT_BEGIN_NAMESPACE
+using options::slider_value;
-QDataStream& operator << (QDataStream& out, const options::slider_value& v)
+QDataStream& operator << (QDataStream& out, const slider_value& v)
{
out << v.cur()
<< v.min()
@@ -98,17 +83,16 @@ QDataStream& operator << (QDataStream& out, const options::slider_value& v)
return out;
}
-QDebug operator << (QDebug dbg, const options::slider_value& val)
+QDebug operator << (QDebug dbg, const slider_value& v)
{
- return dbg << val.cur();
+ return dbg << QStringLiteral("slider_value(cur=%1, min=%2, max=%3)")
+ .arg(v.cur()).arg(v.min()).arg(v.max()).toUtf8().constData();
}
-QDataStream& operator >> (QDataStream& in, options::slider_value& v)
+QDataStream& operator >> (QDataStream& in, slider_value& v)
{
double cur = 0, min = 0, max = 0;
in >> cur >> min >> max;
- v = options::slider_value(cur, min, max);
+ v = slider_value(cur, min, max);
return in;
}
-
-QT_END_NAMESPACE
diff --git a/options/slider.hpp b/options/slider.hpp
index f3ba44f8..1e721ae0 100644
--- a/options/slider.hpp
+++ b/options/slider.hpp
@@ -8,10 +8,11 @@
#pragma once
#include "export.hpp"
-#include "compat/macros.hpp"
-#include <QMetaType>
+#include <type_traits>
+
#include <QDataStream>
+#include <QMetaType>
#include <QDebug>
namespace options
@@ -20,42 +21,28 @@ namespace options
{
double cur_, min_, max_;
- template<typename t>
- using arith_conversion_t = std::enable_if_t<std::is_arithmetic_v<std::decay_t<t>>, std::decay_t<t>>;
public:
slider_value(double cur, double min, double max);
- template<typename t, typename u, typename v> slider_value(t cur, u min, v max) :
- cur_(double(cur)),
- min_(double(min)),
- max_(double(max))
- {}
-
- template<typename t>
- never_inline
- explicit operator arith_conversion_t<t>() const
- {
- return t(cur_);
- }
-
- slider_value(const slider_value& v);
- slider_value();
- slider_value& operator=(const slider_value& v);
+ template<typename t, typename u, typename v>
+ slider_value(t cur, u min, v max) :
+ slider_value((double)cur, (double)min, (double)max) {}
+
+ slider_value& operator=(const slider_value& v) = default;
+ slider_value(const slider_value& v) = default;
+ slider_value() : slider_value{0, 0, 0} {}
+
bool operator==(const slider_value& v) const;
- operator double() const { return cur_; }
- double cur() const { return cur_; }
- double min() const { return min_; }
- double max() const { return max_; }
+ bool operator!=(const slider_value& v) const;
+ constexpr operator double() const { return cur_; }
+ constexpr double cur() const { return cur_; }
+ constexpr double min() const { return min_; }
+ constexpr double max() const { return max_; }
slider_value update_from_slider(int pos, int q_min, int q_max) const;
int to_slider_pos(int q_min, int q_max) const;
};
}
-QT_BEGIN_NAMESPACE
-
-QDebug operator << (QDebug dbg, const options::slider_value& val);
-QDataStream& operator << (QDataStream& out, const options::slider_value& v);
-QDataStream& operator >> (QDataStream& in, options::slider_value& v);
-
-QT_END_NAMESPACE
-
+OTR_OPTIONS_EXPORT QDebug operator << (QDebug dbg, const options::slider_value& val);
+OTR_OPTIONS_EXPORT QDataStream& operator << (QDataStream& out, const options::slider_value& v);
+OTR_OPTIONS_EXPORT QDataStream& operator >> (QDataStream& in, options::slider_value& v);
diff --git a/options/tie.cpp b/options/tie.cpp
index baa8bb82..adf26b53 100644
--- a/options/tie.cpp
+++ b/options/tie.cpp
@@ -8,39 +8,45 @@
#include "tie.hpp"
#include "compat/run-in-thread.hpp"
+#include "compat/macros.h"
+
+#include "value-traits.hpp"
+
+#include <cmath>
namespace options {
-OTR_OPTIONS_EXPORT void tie_setting(value<int>& v, QComboBox* cb)
+void tie_setting(value<int>& v, QComboBox* cb)
{
cb->setCurrentIndex(v);
v = cb->currentIndex();
- base_value::connect(cb, SIGNAL(currentIndexChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE);
- base_value::connect(&v, SIGNAL(valueChanged(int)), cb, SLOT(setCurrentIndex(int)), v.SAFE_CONNTYPE);
+ value_::connect(cb, SIGNAL(currentIndexChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE);
+ value_::connect(&v, SIGNAL(valueChanged(int)), cb, SLOT(setCurrentIndex(int)), v.SAFE_CONNTYPE);
}
-OTR_OPTIONS_EXPORT void tie_setting(value<QString>& v, QComboBox* cb)
+void tie_setting(value<QString>& v, QComboBox* cb)
{
cb->setCurrentText(v);
v = cb->currentText();
- base_value::connect(cb, SIGNAL(currentTextChanged(QString)), &v, SLOT(setValue(const QString&)), v.DIRECT_CONNTYPE);
- base_value::connect(&v, SIGNAL(valueChanged(const QString&)), cb, SLOT(setCurrentText(const QString&)), v.SAFE_CONNTYPE);
+ auto set_current_text = [cb, &v](const QString& str) {
+ cb->setCurrentText(str);
+ v = cb->currentText();
+ };
+ value_::connect(cb, &QComboBox::currentTextChanged, &v, v.value_changed<QString>(), v.DIRECT_CONNTYPE);
+ value_::connect(&v, v.value_changed<QString>(), cb, set_current_text, v.SAFE_CONNTYPE);
}
-OTR_OPTIONS_EXPORT void tie_setting(value<QVariant>& v, QComboBox* cb)
+void tie_setting(value<QVariant>& v, QComboBox* cb)
{
auto set_idx = [cb](const QVariant& var) {
const int sz = cb->count();
int idx = -1;
for (int k = 0; k < sz; k++)
- {
- if (cb->itemData(k) == var)
- {
+ if (cb->itemData(k) == var) {
idx = k;
break;
}
- }
cb->setCurrentIndex(idx);
return idx;
};
@@ -50,65 +56,65 @@ OTR_OPTIONS_EXPORT void tie_setting(value<QVariant>& v, QComboBox* cb)
if (idx != -1)
v = cb->itemData(idx);
else
- v = QVariant(QVariant::Invalid);
-
- base_value::connect(cb, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
- &v, [cb, &v](int idx) {
- v = cb->itemData(idx);
- }, v.DIRECT_CONNTYPE);
- base_value::connect(&v, base_value::value_changed<QVariant>(),
- cb,
- [cb, set_idx](const QVariant& var) {
- run_in_thread_sync(cb, [&]() {
- set_idx(var);
- });
- }, v.DIRECT_CONNTYPE);
+ v = {};
+
+ value_::connect(cb, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+ &v, [cb, &v](int idx) { v = cb->itemData(idx); },
+ v.DIRECT_CONNTYPE);
+ value_::connect(&v, value_::value_changed<QVariant>(),
+ cb, [set_idx](const QVariant& var) { set_idx(var); },
+ v.SAFE_CONNTYPE);
}
-// XXX TODO need variant with setEnabled based on lambda retval -- sh 20170524
+void tie_setting(value<bool>& v, QRadioButton* cb)
+{
+ cb->setChecked(v);
+ value_::connect(cb, SIGNAL(toggled(bool)), &v, SLOT(setValue(bool)), v.DIRECT_CONNTYPE);
+ value_::connect(&v, SIGNAL(valueChanged(bool)), cb, SLOT(setChecked(bool)), v.SAFE_CONNTYPE);
+}
-OTR_OPTIONS_EXPORT void tie_setting(value<bool>& v, QCheckBox* cb)
+void tie_setting(value<bool>& v, QCheckBox* cb)
{
cb->setChecked(v);
- base_value::connect(cb, SIGNAL(toggled(bool)), &v, SLOT(setValue(bool)), v.DIRECT_CONNTYPE);
- base_value::connect(&v, SIGNAL(valueChanged(bool)), cb, SLOT(setChecked(bool)), v.SAFE_CONNTYPE);
+ value_::connect(cb, SIGNAL(toggled(bool)), &v, SLOT(setValue(bool)), v.DIRECT_CONNTYPE);
+ value_::connect(&v, SIGNAL(valueChanged(bool)), cb, SLOT(setChecked(bool)), v.SAFE_CONNTYPE);
}
-OTR_OPTIONS_EXPORT void tie_setting(value<double>& v, QDoubleSpinBox* dsb)
+void tie_setting(value<double>& v, QDoubleSpinBox* dsb)
{
dsb->setValue(v);
- base_value::connect(dsb, SIGNAL(valueChanged(double)), &v, SLOT(setValue(double)), v.DIRECT_CONNTYPE);
- base_value::connect(&v, SIGNAL(valueChanged(double)), dsb, SLOT(setValue(double)), v.SAFE_CONNTYPE);
+ value_::connect(dsb, SIGNAL(valueChanged(double)), &v, SLOT(setValue(double)), v.DIRECT_CONNTYPE);
+ value_::connect(&v, SIGNAL(valueChanged(double)), dsb, SLOT(setValue(double)), v.SAFE_CONNTYPE);
}
-OTR_OPTIONS_EXPORT void tie_setting(value<int>& v, QSpinBox* sb)
+void tie_setting(value<int>& v, QSpinBox* sb)
{
sb->setValue(v);
- base_value::connect(sb, SIGNAL(valueChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE);
- base_value::connect(&v, SIGNAL(valueChanged(int)), sb, SLOT(setValue(int)), v.SAFE_CONNTYPE);
+ value_::connect(sb, SIGNAL(valueChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE);
+ value_::connect(&v, SIGNAL(valueChanged(int)), sb, SLOT(setValue(int)), v.SAFE_CONNTYPE);
}
-OTR_OPTIONS_EXPORT void tie_setting(value<QString>& v, QLineEdit* le)
+void tie_setting(value<QString>& v, QLineEdit* le)
{
le->setText(v);
- base_value::connect(le, SIGNAL(textChanged(QString)), &v, SLOT(setValue(QString)), v.DIRECT_CONNTYPE);
- base_value::connect(&v, base_value::value_changed<QString>(), le, &QLineEdit::setText, v.SAFE_CONNTYPE);
+ value_::connect(le, SIGNAL(textChanged(QString)), &v, SLOT(setValue(QString)), v.DIRECT_CONNTYPE);
+ value_::connect(&v, value_::value_changed<QString>(), le, &QLineEdit::setText, v.SAFE_CONNTYPE);
}
-OTR_OPTIONS_EXPORT void tie_setting(value<QString>& v, QLabel* lb)
+void tie_setting(value<QString>& v, QLabel* lb)
{
lb->setText(v);
- base_value::connect(&v, base_value::value_changed<QString>(), lb, &QLabel::setText, v.SAFE_CONNTYPE);
+ value_::connect(&v, value_::value_changed<QString>(), lb, &QLabel::setText, v.SAFE_CONNTYPE);
}
-OTR_OPTIONS_EXPORT void tie_setting(value<int>& v, QTabWidget* t)
+void tie_setting(value<int>& v, QTabWidget* t)
{
t->setCurrentIndex(v);
- base_value::connect(t, SIGNAL(currentChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE);
- base_value::connect(&v, SIGNAL(valueChanged(int)), t, SLOT(setCurrentIndex(int)), v.SAFE_CONNTYPE);
+ value_::connect(t, SIGNAL(currentChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE);
+ value_::connect(&v, SIGNAL(valueChanged(int)), t, SLOT(setCurrentIndex(int)), v.SAFE_CONNTYPE);
}
-OTR_OPTIONS_EXPORT void tie_setting(value<slider_value>& v, QSlider* w)
+void tie_setting(value<slider_value>& v, QSlider* w)
{
{
const int q_min = w->minimum();
@@ -118,10 +124,7 @@ OTR_OPTIONS_EXPORT void tie_setting(value<slider_value>& v, QSlider* w)
v = v().update_from_slider(w->value(), q_min, q_max);
}
- base_value::connect(w,
- &QSlider::valueChanged,
- &v,
- [=, &v](int pos)
+ value_::connect(w, &QSlider::valueChanged, &v, [=, &v](int pos)
{
run_in_thread_sync(w, [&]()
{
@@ -133,17 +136,17 @@ OTR_OPTIONS_EXPORT void tie_setting(value<slider_value>& v, QSlider* w)
},
v.DIRECT_CONNTYPE);
- base_value::connect(&v,
- base_value::value_changed<slider_value>(),
- w,
- [=, &v](double) {
+ value_::connect(&v,
+ value_::value_changed<slider_value>(),
+ w,
+ [=, &v](double) {
run_in_thread_sync(w, [=, &v]()
{
const int q_min = w->minimum();
const int q_max = w->maximum();
- const int pos = v().to_slider_pos(q_min, q_max);
+ const int pos = v->to_slider_pos(q_min, q_max);
+ v = v->update_from_slider(pos, q_min, q_max);
w->setValue(pos);
- v = v().update_from_slider(w->value(), q_min, q_max);
});
},
v.DIRECT_CONNTYPE);
diff --git a/options/tie.hpp b/options/tie.hpp
index 4ebc1846..194a3a5d 100644
--- a/options/tie.hpp
+++ b/options/tie.hpp
@@ -20,6 +20,7 @@
#include <QLineEdit>
#include <QLabel>
#include <QTabWidget>
+#include <QRadioButton>
#include <cmath>
@@ -31,39 +32,43 @@
namespace options {
template<typename t>
-std::enable_if_t<std::is_enum<t>::value>
-tie_setting(value<t>& v, QComboBox* cb)
+std::enable_if_t<std::is_enum_v<t>> tie_setting(value<t>& v, QComboBox* cb)
{
cb->setCurrentIndex(cb->findData(int(static_cast<t>(v))));
v = static_cast<t>(cb->currentData().toInt());
- base_value::connect(cb,
- static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
- &v,
- [&v, cb](int idx)
- {
- run_in_thread_sync(cb,
- [&]() {
- v = static_cast<t>(cb->itemData(idx).toInt());
- });
- },
- v.DIRECT_CONNTYPE);
- base_value::connect(&v, base_value::value_changed<int>(),
- cb, [cb](int x) {
- run_in_thread_sync(cb, [&]() { cb->setCurrentIndex(cb->findData(x)); });
- },
- v.DIRECT_CONNTYPE);
+ value_::connect(cb, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+ &v, [&v, cb](int idx) { v = static_cast<t>(cb->itemData(idx).toInt()); },
+ v.DIRECT_CONNTYPE);
+
+ value_::connect(&v, value_::value_changed<int>(),
+ cb, [cb](int x) { cb->setCurrentIndex(cb->findData(x)); },
+ v.SAFE_CONNTYPE);
+}
+
+template<typename t, typename From, typename To>
+void tie_setting(value<t>& v, QComboBox* cb, From&& fn_to_index, To&& fn_to_value)
+{
+ cb->setCurrentIndex(fn_to_index(v));
+ v = fn_to_value(cb->currentIndex(), cb->currentData());
+
+ value_::connect(cb, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+ &v, [&v, cb, fn_to_value](int idx) { v = fn_to_value(idx, cb->currentData()); },
+ v.DIRECT_CONNTYPE);
+ value_::connect(&v, value_::value_changed<t>(),
+ cb, [cb, fn_to_index](detail::cv_qualified<t>& v) { cb->setCurrentIndex(fn_to_index(v)); },
+ v.SAFE_CONNTYPE);
}
template<typename t, typename F>
void tie_setting(value<t>& v, QLabel* lb, F&& fun)
{
- auto closure = [=](cv_qualified<t> x) { lb->setText(fun(x)); };
+ auto closure = [lb, fun](detail::cv_qualified<t> v) { lb->setText(fun(v)); };
closure(v());
- base_value::connect(&v, base_value::value_changed<t>(),
- lb, closure,
- v.SAFE_CONNTYPE);
+ value_::connect(&v, value_::value_changed<t>(),
+ lb, closure,
+ v.SAFE_CONNTYPE);
}
template<typename t, typename F>
@@ -74,15 +79,16 @@ void tie_setting(value<t>& v, QObject* obj, F&& fun)
fun(v());
- base_value::connect(&v, base_value::value_changed<t>(),
- obj, fun,
- v.DIRECT_CONNTYPE);
+ value_::connect(&v, value_::value_changed<t>(),
+ obj, fun,
+ v.DIRECT_CONNTYPE);
}
OTR_OPTIONS_EXPORT void tie_setting(value<int>& v, QComboBox* cb);
OTR_OPTIONS_EXPORT void tie_setting(value<QString>& v, QComboBox* cb);
OTR_OPTIONS_EXPORT void tie_setting(value<QVariant>& v, QComboBox* cb);
OTR_OPTIONS_EXPORT void tie_setting(value<bool>& v, QCheckBox* cb);
+OTR_OPTIONS_EXPORT void tie_setting(value<bool>& v, QRadioButton* cb);
OTR_OPTIONS_EXPORT void tie_setting(value<double>& v, QDoubleSpinBox* dsb);
OTR_OPTIONS_EXPORT void tie_setting(value<int>& v, QSpinBox* sb);
OTR_OPTIONS_EXPORT void tie_setting(value<QString>& v, QLineEdit* le);
diff --git a/options/value-traits.hpp b/options/value-traits.hpp
index ac5f6e5c..145cd924 100644
--- a/options/value-traits.hpp
+++ b/options/value-traits.hpp
@@ -1,49 +1,122 @@
-#include "export.hpp"
+#pragma once
#include "slider.hpp"
+#include "export.hpp"
-#include <QString>
-
+#include <cmath>
#include <type_traits>
-namespace options {
-namespace detail {
+#include <QString>
+
+namespace options::detail {
+template<typename t>
+using cv_qualified =
+ std::conditional_t<std::is_fundamental_v<std::remove_cvref_t<t>>,
+ std::remove_cvref_t<t>,
+ std::add_lvalue_reference_t<std::add_const_t<std::remove_cvref_t<t>>>>;
template<typename t, typename u = t, typename Enable = void>
struct default_value_traits
{
- using element_type = std::decay_t<t>;
- using value_type = std::decay_t<u>;
+ using value_type = t;
+ using stored_type = u;
+
+ static inline
+ value_type value_with_default(cv_qualified<value_type> val, cv_qualified<value_type>)
+ {
+ return val;
+ }
+
+ static inline
+ value_type value_from_storage(cv_qualified<stored_type> x)
+ {
+ return static_cast<value_type>(x);
+ }
+
+ static inline
+ stored_type storage_from_value(cv_qualified<value_type> val)
+ {
+ return static_cast<stored_type>(val);
+ }
+
+ static inline
+ value_type value_from_qvariant(const QVariant& x)
+ {
+ return value_from_storage(storage_from_qvariant(x));
+ }
+
+ static inline
+ QVariant qvariant_from_value(cv_qualified<value_type> val)
+ {
+ return qvariant_from_storage(storage_from_value(val));
+ }
+
+ static constexpr inline
+ value_type pass_value(cv_qualified<value_type> x)
+ {
+ if constexpr(std::is_same_v<value_type, stored_type>)
+ return x;
+ else
+ return value_from_storage(storage_from_value(x));
+ }
- static inline value_type from_value(const value_type& val, const value_type&) { return val; }
- static inline value_type from_storage(const element_type& x) { return static_cast<value_type>(x); }
- static inline element_type to_storage(const value_type& val) { return static_cast<element_type>(val); }
+ static inline
+ stored_type storage_from_qvariant(const QVariant& x)
+ {
+ // XXX TODO
+ return x.value<stored_type>();
+ }
+
+ static inline
+ QVariant qvariant_from_storage(cv_qualified<stored_type> val)
+ {
+ // XXX TODO
+ return QVariant::fromValue<stored_type>(val);
+ }
+
+ static inline
+ bool is_equal(cv_qualified<value_type> x, cv_qualified<value_type> y)
+ {
+ return x == y;
+ }
};
-template<typename t, typename u = t, typename Enable = void>
-struct value_traits : default_value_traits<t, u, Enable>
+template<typename t, typename Enable = void>
+struct value_traits : default_value_traits<t> {};
+
+template<>
+inline
+bool default_value_traits<double>::is_equal(double x, double y)
{
-};
+ return std::fabs(x - y) < 1e-6;
+}
+
+template<> struct value_traits<float, double> : default_value_traits<float> {};
template<>
-struct value_traits<slider_value> : default_value_traits<slider_value>
+inline
+bool default_value_traits<float, double>::is_equal(float x, float y)
{
- static inline slider_value from_value(const slider_value& val, const slider_value& def)
- {
- return slider_value(val.cur(), def.min(), def.max());
- }
-};
+ return std::fabs(x - y) < 1e-6f;
+}
-// Qt uses int a lot in slots so use it for all enums
-template<typename t>
-struct value_traits<t, t, std::enable_if_t<std::is_enum_v<t>>> : public default_value_traits<int, t>
+template<>
+inline
+slider_value default_value_traits<slider_value>::value_with_default(cv_qualified<slider_value> val, cv_qualified<slider_value> def)
{
-};
+ return { val.cur(), def.min(), def.max() };
+}
template<>
-struct value_traits<float> : public default_value_traits<float, double, void>
+inline
+bool default_value_traits<slider_value>::is_equal(cv_qualified<slider_value> x, cv_qualified<slider_value> y)
{
-};
+ return value_traits<double>::is_equal(x.cur(), y.cur());
+}
+
+// Qt uses int a lot in slots so use it for all enums
+template<typename t>
+struct value_traits<t, std::enable_if_t<std::is_enum_v<t>>> : default_value_traits<t, int> {};
+
+} // ns options::detail
-} // ns detail
-} // ns options
diff --git a/options/value.cpp b/options/value.cpp
index 5796dcd6..81a094e6 100644
--- a/options/value.cpp
+++ b/options/value.cpp
@@ -1,5 +1,5 @@
// instantiate the "template class" value<t> symbols
-#define OTR_OPT_VALUE OTR_TEMPLATE_EXPORT
+#define OTR_OPTIONS_INST_VALUE OTR_TEMPLATE_EXPORT
#include "value.hpp"
diff --git a/options/value.hpp b/options/value.hpp
index c8fb02ca..9a7487b8 100644
--- a/options/value.hpp
+++ b/options/value.hpp
@@ -14,157 +14,208 @@
#include "slider.hpp"
#include "base-value.hpp"
#include "value-traits.hpp"
-#include "compat/macros.hpp"
-#include <cstdio>
#include <type_traits>
-#include <typeinfo>
-#include <typeindex>
#include <utility>
-#include <QVariant>
-#include <QString>
-#include <QPointF>
-#include <QList>
-#include <QMutex>
+#include <QMetaType>
+
+namespace options::detail {
+ template<typename t>
+ class dereference_wrapper final
+ {
+ t x;
+ public:
+ constexpr t const* operator->() const { return &x; }
+ constexpr t* operator->() { return &x; }
+ constexpr explicit dereference_wrapper(t&& x) : x(x) {}
+ };
+} // ns options::detail
namespace options {
template<typename t>
-class value final : public base_value
+class value final : public value_
{
- using traits = detail::value_traits<t, t, void>;
- using element_type = typename traits::element_type;
+ static_assert(std::is_same_v<t, std::remove_cvref_t<t>>);
+ mutable QMutex mtx;
+ const t def;
+ mutable t cached_value;
+ using traits = detail::value_traits<t>;
- static bool is_equal(const QVariant& val1, const QVariant& val2)
+ never_inline
+ auto get() const noexcept
{
- return val1.value<element_type>() == val2.value<element_type>();
+ if (!b->contains(self_name))
+ return traits::pass_value(def);
+
+ QVariant variant = b->get_variant(self_name);
+
+ if (variant.isNull() || !variant.isValid())
+ return traits::pass_value(def);
+
+ return traits::pass_value(traits::value_with_default(traits::value_from_qvariant(variant), def));
}
never_inline
- t get() const
+ void store_variant(QVariant&& value) noexcept override
{
- if (self_name.isEmpty())
- return def;
+ if (traits::is_equal(get(), traits::value_from_qvariant(value)))
+ return;
- QVariant variant = b->get<QVariant>(self_name);
+ if (is_null())
+ return;
- if (!b->contains(self_name) || variant.type() == QVariant::Invalid)
- return def;
+ if (value.isValid() && !value.isNull())
+ b->store_kv(self_name, value);
+ else
+ b->store_kv(self_name, traits::qvariant_from_value(def));
+ }
- const element_type x(variant.value<element_type>());
+ never_inline
+ void store_variant(const QVariant& value) noexcept override
+ {
+ QVariant copy{value};
+ store_variant(std::move(copy));
+ }
- return traits::from_value(traits::from_storage(x), def);
+ bool is_null() const
+ {
+ return self_name.isEmpty() || b->name().isEmpty();
}
public:
- never_inline
- t operator=(const t& datum)
+ QVariant get_variant() const noexcept override
{
- if (self_name.isEmpty())
- return def;
-
- if (datum != get())
- store(traits::to_storage(datum));
+ if (QVariant ret{b->get_variant(self_name)}; ret.isValid() && !ret.isNull())
+ return ret;
- return datum;
+ return traits::qvariant_from_value(def);
}
- static constexpr inline Qt::ConnectionType DIRECT_CONNTYPE = Qt::DirectConnection;
- static constexpr inline Qt::ConnectionType SAFE_CONNTYPE = Qt::QueuedConnection;
+ never_inline
+ void notify_() const override
+ {
+ auto x = get();
+ {
+ QMutexLocker l(&mtx);
+ cached_value = x;
+ }
+ maybe_trace("notify +");
+ emit valueChanged(traits::storage_from_value(x));
+ maybe_trace("notify -");
+ }
never_inline
- value(bundle b, const QString& name, t def) :
- base_value(b, name, &is_equal, std::type_index(typeid(element_type))),
- def(def)
- {
- if (!self_name.isEmpty())
- QObject::connect(b.get(), SIGNAL(reloading()),
- this, SLOT(reload()),
- DIRECT_CONNTYPE);
+ void notify() const override
+ {
+ if (is_null())
+ return;
+
+ auto x = get();
+ {
+ QMutexLocker l(&mtx);
+ if (traits::is_equal(x, cached_value))
+ {
+ //maybe_trace("notify ~");
+ return;
+ }
+ else
+ {
+ cached_value = x;
+ l.unlock();
+ maybe_trace("notify +");
+ emit valueChanged(traits::storage_from_value(x));
+ maybe_trace("notify -");
+ }
+ }
}
- template<unsigned k>
- inline value(bundle b, const char (&name)[k], t def) : value(b, QLatin1String(name, k-1), def)
+ auto& operator=(t&& datum) noexcept
{
- static_assert(k > 0, "");
+ if (is_null())
+ return *this;
+
+ store_variant(traits::qvariant_from_value(traits::pass_value(datum)));
+ maybe_trace("set-value");
+ return *this;
}
- never_inline
- t default_value() const
+ auto& operator=(const t& datum) noexcept
{
- return def;
+ if (is_null())
+ return *this;
+
+ t copy{datum};
+ *this = std::move(copy);
+ return *this;
}
- never_inline
- void set_to_default() override
+ auto& operator=(const value<t>& datum) noexcept
{
- *this = def;
+ *this = *datum;
+ return *this;
}
- never_inline
- operator t() const { return get(); }
+ static constexpr Qt::ConnectionType DIRECT_CONNTYPE = Qt::DirectConnection;
+ static constexpr Qt::ConnectionType SAFE_CONNTYPE = Qt::QueuedConnection;
- never_inline
- t operator->() const
+ value(bundle b, const QString& name, t def) noexcept : value_(b, name), def(std::move(def)), cached_value{get()}
{
- return get();
}
- never_inline
- void reload() override
+ //value(const value<t>& other) noexcept : value{other.b, other.self_name, other.def} {}
+ value(const value<t>&) = delete;
+
+ t default_value() const
{
- if (!self_name.isEmpty())
- *this = static_cast<t>(*this);
+ return def;
}
- never_inline
- void bundle_value_changed() const override
+ void set_to_default() noexcept override
{
- if (!self_name.isEmpty())
- emit valueChanged(traits::to_storage(get()));
+ *this = def;
}
- never_inline
- t operator()() const
+ operator t() const { return get(); }
+
+ template<typename u>
+ explicit force_inline operator u() const { return to<u>(); }
+
+ auto operator->() const noexcept
{
- return get();
+ return detail::dereference_wrapper<t>{get()};
}
+ force_inline auto operator()() const noexcept { return get(); }
+ force_inline auto operator*() const noexcept { return get(); }
+
template<typename u>
- never_inline
- u to() const
+ u to() const noexcept
{
return static_cast<u>(get());
}
-
-private:
- const t def;
};
-#if defined _MSC_VER
-
-# if !defined OTR_OPT_VALUE
-# define OTR_OPT_VALUE OTR_TEMPLATE_IMPORT
-# endif
-
- OTR_OPT_VALUE value<double>;
- OTR_OPT_VALUE value<float>;
- OTR_OPT_VALUE value<int>;
- OTR_OPT_VALUE value<bool>;
- OTR_OPT_VALUE value<QString>;
- OTR_OPT_VALUE value<slider_value>;
- OTR_OPT_VALUE value<QPointF>;
- OTR_OPT_VALUE value<QVariant>;
-
- OTR_OPT_VALUE value<QList<double>>;
- OTR_OPT_VALUE value<QList<float>>;
- OTR_OPT_VALUE value<QList<int>>;
- OTR_OPT_VALUE value<QList<bool>>;
- OTR_OPT_VALUE value<QList<QString>>;
- OTR_OPT_VALUE value<QList<slider_value>>;
- OTR_OPT_VALUE value<QList<QPointF>>;
-
+#if !defined OTR_OPTIONS_INST_VALUE
+# define OTR_OPTIONS_INST_VALUE OTR_TEMPLATE_IMPORT
#endif
+OTR_OPTIONS_INST_VALUE(value<double>)
+OTR_OPTIONS_INST_VALUE(value<float>)
+OTR_OPTIONS_INST_VALUE(value<int>)
+OTR_OPTIONS_INST_VALUE(value<bool>)
+OTR_OPTIONS_INST_VALUE(value<QString>)
+OTR_OPTIONS_INST_VALUE(value<slider_value>)
+OTR_OPTIONS_INST_VALUE(value<QPointF>)
+OTR_OPTIONS_INST_VALUE(value<QVariant>)
+OTR_OPTIONS_INST_VALUE(value<QList<double>>)
+OTR_OPTIONS_INST_VALUE(value<QList<float>>)
+OTR_OPTIONS_INST_VALUE(value<QList<int>>)
+OTR_OPTIONS_INST_VALUE(value<QList<bool>>)
+OTR_OPTIONS_INST_VALUE(value<QList<QString>>)
+OTR_OPTIONS_INST_VALUE(value<QList<slider_value>>)
+OTR_OPTIONS_INST_VALUE(value<QList<QPointF>>)
+OTR_OPTIONS_INST_VALUE(value<QList<QVariant>>)
+
} // ns options
diff --git a/pose-widget/CMakeLists.txt b/pose-widget/CMakeLists.txt
index fd109bc3..28dc918b 100644
--- a/pose-widget/CMakeLists.txt
+++ b/pose-widget/CMakeLists.txt
@@ -1,2 +1,2 @@
otr_module(pose-widget BIN)
-target_link_libraries(opentrack-pose-widget)
+target_link_libraries(${self})
diff --git a/pose-widget/ReadMe.txt b/pose-widget/ReadMe.txt
new file mode 100644
index 00000000..c504ff41
--- /dev/null
+++ b/pose-widget/ReadMe.txt
@@ -0,0 +1,26 @@
+Hi everyone!
+
+In the extreme version of OpenTrack-2.3.12, the Octopus pose is still displayed incorrectly.
+The pose-widget is responsible for displaying the Octopus pose in OpenTrack. I have fixed this widget.
+
+Fixed bugs:
+- The turns and movements of the Octopussy are now performed truly independently of each other, as it should be in 6DOF.
+- When cornering, there is no "gimbal lock" at Pitch = +/- 90 degrees.
+- Fixed directions of axes of rotations and positions. Now the Octopus pose displays the actual direction of view on the plane.
+- Fixed display of the back (green) surface of the Octopus. Previously, it was displayed mirrored.
+
+Additional features:
+- Applied "perspective projection", the picture becomes more voluminous.
+- Added lighting effect from above, with the same purpose.
+- Added background fill for the widget. This makes it possible to see the borders of the widget.
+- Added X and Y axes. This helps to estimate how far the Octopus is deviated from the center.
+- Added [Mirror] checkbox, mirroring positions and rotations. It is often more convenient to observe the Octopussy's mirror pose.
+- If before compilation in the file "pose-widget.hpp" include line 19: "#define TEST", then a rectangular frame will be drawn around the Octopus. This is useful when testing a pose-widget to assess distortion and size.
+
+The corrected pose-widget now displays the Octopus pose correctly and can be used to check OpenTrack settings, sometimes even without launching the flight simulator.
+
+A video of the corrected pose-widget is available here:
+
+https://youtu.be/my4_VOwGmq4
+
+fixed by GO63-samara <go1@list.ru> <github.com/GO63-samara> 2020
diff --git a/pose-widget/images/side1.png b/pose-widget/images/side1.png
index d15c3658..2955bc01 100644
--- a/pose-widget/images/side1.png
+++ b/pose-widget/images/side1.png
Binary files differ
diff --git a/pose-widget/images/side6.png b/pose-widget/images/side6.png
index 7338690d..3bae0e50 100644
--- a/pose-widget/images/side6.png
+++ b/pose-widget/images/side6.png
Binary files differ
diff --git a/pose-widget/lang/de_DE.ts b/pose-widget/lang/de_DE.ts
new file mode 100644
index 00000000..1552582e
--- /dev/null
+++ b/pose-widget/lang/de_DE.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+</TS>
diff --git a/pose-widget/lang/zh_CN.ts b/pose-widget/lang/zh_CN.ts
index 6401616d..e5ca8aa9 100644
--- a/pose-widget/lang/zh_CN.ts
+++ b/pose-widget/lang/zh_CN.ts
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
</TS>
diff --git a/pose-widget/pose-widget.cpp b/pose-widget/pose-widget.cpp
index 60d41aa5..ac3aa74a 100644
--- a/pose-widget/pose-widget.cpp
+++ b/pose-widget/pose-widget.cpp
@@ -6,420 +6,134 @@
*/
#include "pose-widget.hpp"
-#include "compat/timer.hpp"
-#include "compat/sleep.hpp"
#include "compat/check-visible.hpp"
#include "compat/math.hpp"
-#include <cmath>
-#include <algorithm>
-
#include <QPainter>
-#include <QPaintEvent>
+#include <QtEvents>
#include <QDebug>
+#include <QQuaternion>
+#include <QMatrix4x4>
-// XXX this needs rewriting in terms of scanline rendering -sh 20180105
-// see: <https://mikro.naprvyraz.sk/docs/Coding/2/TEXTURE4.TXT>
-
-using namespace pose_widget_impl;
-
-pose_transform::pose_transform(QWidget* dst) :
- dst(dst),
- front(":/images/side1.png", nullptr),
- back(":/images/side6.png", nullptr),
- image(w, h, QImage::Format_ARGB32),
- image2(w, h, QImage::Format_ARGB32),
- fresh(false)
-{
- image.fill(Qt::transparent);
- image2.fill(Qt::transparent);
-}
+namespace pose_widget_impl {
-pose_transform::~pose_transform()
+pose_widget::pose_widget(QWidget* parent) : QWidget(parent)
{
- requestInterruption();
- wait();
-}
-
-void pose_widget::paintEvent(QPaintEvent* event)
-{
- QPainter p(this);
-
- xform.with_image_lock([&](const QImage& image)
- {
- p.drawImage(event->rect(), image, QRect(0, 0, pose_transform::w, pose_transform::h));
- });
-
- if (!xform.isRunning())
- xform.start(QThread::LowPriority);
-}
-
-void pose_transform::run()
-{
- for (;;)
- {
- if (isInterruptionRequested())
- break;
-
- {
- lock_guard l(mtx);
+ QPainter p;
+#ifdef TEST
+ //draw rectangle frame around of Octopus, only if TEST defined
+ p.begin(&front);
+ p.setPen(QPen(Qt::red, 3, Qt::SolidLine));
+ p.drawRect(0, 0, front.width()-1, front.height()-1);
+ p.end();
- if (fresh)
- goto end;
-
- rotation = rotation_;
- translation = translation_;
- }
-
- project_quad_texture();
-
-end:
- portable::sleep(23);
- }
-}
-
-pose_widget::pose_widget(QWidget* parent) : QWidget(parent), xform(this)
-{
- rotate_sync(0,0,0, 0,0,0);
-}
-
-pose_widget::~pose_widget()
-{
-}
-
-void pose_widget::rotate_async(double xAngle, double yAngle, double zAngle, double x, double y, double z)
-{
- if (!check_is_visible())
- return;
-
- bool expected = true;
- if (xform.fresh.compare_exchange_weak(expected, false))
- {
- repaint();
- xform.rotate_async(xAngle, yAngle, zAngle, x, y, z);
- }
-}
-
-template<typename F>
-void pose_transform::with_rotate(F&& fun, double xAngle, double yAngle, double zAngle, double x, double y, double z)
-{
- using std::sin;
- using std::cos;
-
- constexpr double d2r = M_PI / 180;
-
- euler::euler_t euler(-zAngle * d2r, xAngle * d2r, -yAngle * d2r);
- euler::rmat r = euler::euler_to_rmat(euler);
-
- lock_guard l(mtx);
-
- for (int i = 0; i < 3; i++)
- for (int j = 0; j < 3; j++)
- rotation_(i, j) = num(r(i, j));
-
- translation_ = vec3(x, y, z);
-
- fun();
-}
+ p.begin(&back);
+ p.setPen(QPen(Qt::darkGreen, 3, Qt::SolidLine));
+ p.drawRect(0, 0, back.width()-1, back.height()-1);
+ p.end();
+#endif
-void pose_widget::rotate_sync(double xAngle, double yAngle, double zAngle, double x, double y, double z)
-{
- xform.rotate_sync(xAngle, yAngle, zAngle, x, y, z);
-}
+ //draw Octopus shine
+ shine.fill(QColor(255,255,255));
+ p.begin(&shine);
+ p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
+ p.drawImage(QPointF(0,0), front);
+ p.end();
-void pose_transform::rotate_async(double xAngle, double yAngle, double zAngle, double x, double y, double z)
-{
- with_rotate([this]() {}, xAngle, yAngle, zAngle, x, y, z);
-}
+ //draw Octopus shadow
+ shadow.fill(QColor(0,0,0));
+ p.begin(&shadow);
+ p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
+ p.drawImage(QPointF(0,0), front);
+ p.end();
-void pose_transform::rotate_sync(double xAngle, double yAngle, double zAngle, double x, double y, double z)
-{
- with_rotate([this]() {
- rotation = rotation_;
- translation = translation_;
- project_quad_texture();
- dst->repaint();
- }, xAngle, yAngle, zAngle, x, y, z);
+ mirror.setFocusPolicy(Qt::NoFocus);
}
-Triangle::Triangle(const vec2& p1, const vec2& p2, const vec2& p3)
+void pose_widget::present(double yaw, double pitch, double roll, double x, double y, double z)
{
- origin = p1;
+ T = { x, y, z };
+ R = { yaw, pitch, roll };
- v0 = vec2(p3 - p1);
- v1 = vec2(p2 - p1);
-
- dot00 = v0.dot(v0);
- dot01 = v0.dot(v1);
- dot11 = v1.dot(v1);
-
- const num denom = dot00 * dot11 - dot01 * dot01;
-
- invDenom = 1 / denom;
+ repaint();
}
-bool Triangle::barycentric_coords(const vec2& px, vec2& uv, int& i) const
+void pose_widget::resizeEvent(QResizeEvent *event)
{
- i = 0;
- const vec2 v2 = px - origin;
- const num dot12 = v1.dot(v2);
- const num dot02 = v0.dot(v2);
- num u = (dot11 * dot02 - dot01 * dot12) * invDenom;
- num v = (dot00 * dot12 - dot01 * dot02) * invDenom;
-
- if (!(u >= 0 && v >= 0))
- return false;
+ // adapt to widget size
+ float w = event->size().width();
+ float h = event->size().height();
- if (u + v > 1)
- {
- i = 1;
-
- u = 1 - u;
- v = 1 - v;
- }
- uv = vec2(u, v);
- return u >= 0 && v >= 0 && u + v <= 1;
+ // move the mirror checkbox in the lower right corner of the widget
+ mirror.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ mirror.move(w - mirror.width(), h - mirror.height());
}
-std::pair<vec2i, vec2i> pose_transform::get_bounds(const vec2& size)
+void pose_widget::paintEvent(QPaintEvent*)
{
- const int x = size.x(), y = size.y();
-
- const vec3 corners[] = {
- { -x, -y, 0 },
- { x, -y, 0 },
- { -x, y, 0 },
- { x, y, 0 },
- };
+ // widget settings:
+ constexpr float scale = 0.5; // scale of Octopus height, when x = y = z = 0.0
+ constexpr float XYZmax = 50.0; // -XYZmax < x,y,z < +XYZmax (offset the Octopus by one body)
+ constexpr float Kz = 0.25; // Z scale change limit (simulate camera focus length)
- vec2 min(w-1, h-1), max(0, 0);
+ // get a local copy of input data
+ auto [ yaw, pitch, roll ] = R;
+ auto [ x, y, z ] = T;
- for (unsigned k = 0; k < 4; k++)
- {
- const vec2 pt = project(corners[k]) + vec2(w/2, h/2);
-
- min.x() = std::fmin(min.x(), pt.x());
- min.y() = std::fmin(min.y(), pt.y());
-
- max.x() = std::fmax(max.x(), pt.x());
- max.y() = std::fmax(max.y(), pt.y());
- }
-
- min.x() = clamp(min.x(), 0, w-1);
- min.y() = clamp(min.y(), 0, h-1);
- max.x() = clamp(max.x(), 0, w-1);
- max.y() = clamp(max.y(), 0, h-1);
-
-#if 0
- {
- QPainter p(&image);
- p.drawRect(min.x(), min.y(), max.x()-min.x(), max.y()-min.y());
- }
-#endif
-
- return std::make_pair(vec2i(iround(min.x()), iround(min.y())),
- vec2i(iround(max.x()), iround(max.y())));
-}
-
-void pose_transform::project_quad_texture()
-{
- vec3 bgcolor;
- {
- QColor bg = dst->palette().background().color();
- image.fill(bg);
- bgcolor = vec3(bg.red(), bg.green(), bg.blue());
- }
-
- num dir;
- vec2 pt[4];
-
- vec2i min, max;
-
- {
- constexpr double c = 85/100.;
-
- const int sx_ = (w - std::max(0, (w - h)/2)) * 5/9;
- const int sy_ = (h - std::max(0, (h - w)/2)) * 5/9;
-
- std::tie(min, max) = get_bounds(vec2(sx_/2.*c, sy_/2));
-
- const vec3 dst_corners[] =
- {
- { -sx_/2. * c, -sy_/2., 0 },
- { sx_/2. * c, -sy_/2., 0 },
- { -sx_/2. * c, sy_/2., 0 },
- { sx_/2. * c, sy_/2., 0 },
- };
-
- for (int i = 0; i < 4; i++)
- pt[i] = project(dst_corners[i]) + vec2(w/2, h/2);
-
- {
- vec3 foo[3];
- for (int i = 0; i < 3; i++)
- foo[i] = project2(dst_corners[i]);
-
- vec3 p1 = foo[1] - foo[0];
- vec3 p2 = foo[2] - foo[0];
- dir = p1.x() * p2.y() - p1.y() * p2.x(); // Z part of the cross product
- }
- }
-
- // rotation of (0, 90, 0) makes it numerically unstable
- if (std::fabs(dir) < 1e-3f)
- {
- lock_guard l(mtx2);
- image.swap(image2);
- fresh = true;
- return;
- }
-
- const QImage& tex = dir < 0 ? back : front;
-
- Triangle t(pt[0], pt[1], pt[2]);
-
- const unsigned orig_pitch = tex.bytesPerLine();
- const unsigned dest_pitch = image.bytesPerLine();
-
- unsigned char const* restrict_ptr orig = tex.constBits();
- unsigned char* restrict_ptr dest = image.bits();
-
- const int orig_depth = tex.depth() / 8;
- const int dest_depth = image.depth() / 8;
- constexpr int const_depth = 4;
-
- if (unlikely(orig_depth != const_depth || dest_depth != const_depth))
- {
- qDebug() << "pose-widget: octopus must be saved as .png with 32 bits depth";
- qDebug() << "pose-widget: target texture must be ARGB32";
- return;
- }
-
- const vec2u dist(max.x() - min.x(), max.y() - min.y());
-
- if (int(uv_vec.size()) < dist.x() * dist.y())
- uv_vec.resize(dist.x() * dist.y());
-
- for (int y = 0; y < dist.y(); y++)
- for (int x = 0; x < dist.x(); x++)
- {
- uv_* restrict_ptr uv = &uv_vec[y * dist.x() + x];
- if (!t.barycentric_coords(vec2(x + min.x(), y + min.y()), uv->coords, uv->i))
- uv->i = -1;
- }
-
- const int ow = tex.width(), oh = tex.height();
-
- vec2 const origs[2][3] =
- {
- {
- { 0, 0 },
- { ow-1, 0 },
- { 0, oh-1 },
- },
- {
- { ow-1, oh-1 },
- vec2(0, oh-1) - vec2(ow-1, oh-1),
- vec2(ow-1, 0) - vec2(ow-1, oh-1),
- }
- };
-
- for (int y_ = 0, dy = dist.y(); y_ < dy; y_++)
- {
- for (int x_ = 0, dx = dist.x(); x_ < dx; x_++)
- {
- const int y = y_ + min.y(), x = x_ + min.x();
- const uv_* restrict_ptr uv__ = &uv_vec[y_ * dx + x_];
-
- if (uv__->i != -1)
- {
- using uc = unsigned char;
-
- vec2 const& uv = uv__->coords;
- int const i = uv__->i;
-
- float fx = origs[i][0].x()
- + uv.x() * origs[i][2].x()
- + uv.y() * origs[i][1].x();
- float fy = origs[i][0].y()
- + uv.x() * origs[i][2].y()
- + uv.y() * origs[i][1].y();
-
-//#define BILINEAR_FILTER
-
-#if defined BILINEAR_FILTER
- const unsigned px_ = fx + 1;
- const unsigned py_ = fy + 1;
-#endif
- const unsigned px = fx;
- const unsigned py = fy;
-
- const unsigned orig_pos = py * orig_pitch + px * const_depth;
-#if defined BILINEAR_FILTER
- const unsigned orig_pos_ = py_ * orig_pitch + px_ * const_depth;
- const unsigned orig_pos__ = py * orig_pitch + px_ * const_depth;
- const unsigned orig_pos___ = py_ * orig_pitch + px * const_depth;
-#endif
-
- // 1, 0 -- ax_, ay
- // 0, 1 -- ax, ay_
- // 1, 1 -- ax_, ay_
- // 0, 0 -- ax, ay
- //const uc alpha = (a1 * ax + a3 * ax_) * ay + (a4 * ax + a2 * ax_) * ay_;
-
-#if defined BILINEAR_FILTER
- const float ax_ = fx - unsigned(fx);
- const float ay_ = fy - unsigned(fy);
- const float ax = 1 - ax_;
- const float ay = 1 - ay_;
-#endif
-
- const unsigned pos = y * dest_pitch + x * const_depth;
-
- if (orig[orig_pos + 3] == uc(255)) // alpha
- for (int k = 0; k < 3; k++)
- dest[pos + k] = orig[orig_pos + k];
- else
- for (int k = 0; k < 3; k++)
- dest[pos + k] = bgcolor(k);
- }
- }
- }
+ QPainter p(this);
+ #ifdef TEST
+ // use antialiasing for correct frame around the Octopus, only if TEST defined
+ p.setRenderHint(QPainter::Antialiasing, true);
+ #endif
{
- lock_guard l2(mtx2);
- image.swap(image2);
- fresh = true;
+ p.fillRect(rect(), palette().brush(backgroundRole()));
+ // draw axes
+ p.save();
+ p.setPen(QPen(Qt::gray, 1, Qt::SolidLine));
+ int w = width(), h = height();
+ p.drawLine(w/2, 0, w/2, h);
+ p.drawLine( 0, h/2, w, h/2);
+ p.restore();
}
-}
-
-vec2 pose_transform::project(const vec3 &point)
-{
- using std::fabs;
-
- vec3 ret = rotation * point;
- num z = std::fmax(num(.5), 1 + translation.z()/-80);
- num w_ = w, h_ = h;
- num x = w_ * translation.x() / 2 / -80;
- if (fabs(x) > w_/2)
- x = x > 0 ? w_/2 : w_/-2;
- num y = h_ * translation.y() / 2 / -80;
- if (fabs(y) > h_/2)
- y = y > 0 ? h_/2 : h_/-2;
- return vec2(z * (ret.x() + x), z * (ret.y() + y));
-}
-
-vec3 pose_transform::project2(const vec3 &point)
-{
- return rotation * point;
-}
-
-template<typename F>
-inline void pose_transform::with_image_lock(F&& fun)
-{
- lock_guard l(mtx2);
-
- fun(image2);
-}
+ // check mirror state
+ if (mirror.checkState() == Qt::Checked) x = -x;
+ else { yaw = -yaw; roll = -roll; }
+ y = -y;
+
+ // rotations
+ QQuaternion q = QQuaternion::fromEulerAngles(pitch, yaw, roll);
+ QMatrix4x4 m = QMatrix4x4(q.toRotationMatrix());
+
+ // x and y positions
+ const float Kxy = (float)front.height() / XYZmax;
+ QVector3D v(Kxy*x, Kxy*y, 0.0);
+ v = m.transposed().map(v);
+ m.translate(v);
+
+ // perspective projection to x-y plane
+ QTransform t = m.toTransform(1024).translate(-.5 * front.width(), -.5 * front.height());
+
+ // z position by setViewport
+ const float mz = scale * height()/front.height()/exp(1.0) * exp(1.0 - z * (Kz/XYZmax));
+ p.setViewport(QRect(.5 * width(), .5 * height(), width()*mz, height()*mz));
+
+ // define forward or backward side by cross product of mapped x and y axes
+ QPointF point0 = t.map(QPointF(0, 0));
+ QPointF x_dir = (t.map(QPointF(1, 0)) -= point0);
+ QPointF y_dir = (t.map(QPointF(0, 1)) -= point0);
+ const bool forward = x_dir.ry()*y_dir.rx() - x_dir.rx()*y_dir.ry() < 0 ? true : false;
+
+ // draw red or green Octopus
+ p.setTransform(t);
+ p.drawImage(QPointF(0,0), forward ? front : back);
+
+ // top lighting simulation
+ const float alpha = sin(pitch * M_PI / 180.0);
+ p.setOpacity(0.333 * fabs(alpha));
+ p.drawImage(QPointF(0,0), forward == (alpha >= 0.0) ? shine : shadow);
+}
+
+} // ns pose_widget_impl
diff --git a/pose-widget/pose-widget.hpp b/pose-widget/pose-widget.hpp
index fa956b47..9152e960 100644
--- a/pose-widget/pose-widget.hpp
+++ b/pose-widget/pose-widget.hpp
@@ -12,96 +12,33 @@
#include "export.hpp"
-#include <tuple>
-#include <mutex>
-#include <atomic>
-#include <vector>
-
#include <QWidget>
-#include <QThread>
#include <QImage>
+#include <QCheckBox>
+//#define TEST
namespace pose_widget_impl {
-using num = float;
-using vec3 = Mat<num, 3, 1>;
-using vec2 = Mat<num, 2, 1>;
-using vec2i = Mat<int, 2, 1>;
-using vec2u = Mat<int, 2, 1>;
-
-using rmat = Mat<num, 3, 3>;
-
using namespace euler;
-using lock_guard = std::unique_lock<std::mutex>;
-
-class pose_widget;
-
-class Triangle {
- num dot00, dot01, dot11, invDenom;
- vec2 v0, v1, origin;
-public:
- Triangle(const vec2& p1, const vec2& p2, const vec2& p3);
- bool barycentric_coords(const vec2& px, vec2& uv, int& i) const;
-};
-
-struct pose_transform final : QThread
-{
- pose_transform(QWidget* dst);
- ~pose_transform();
-
- void rotate_async(double xAngle, double yAngle, double zAngle, double x, double y, double z);
- void rotate_sync(double xAngle, double yAngle, double zAngle, double x, double y, double z);
-
- template<typename F>
- void with_rotate(F&& fun, double xAngle, double yAngle, double zAngle, double x, double y, double z);
-
- void run() override;
-
- vec2 project(const vec3& point);
- vec3 project2(const vec3& point);
- void project_quad_texture();
- std::pair<vec2i, vec2i> get_bounds(const vec2& size);
-
- template<typename F>
- inline void with_image_lock(F&& fun);
-
- rmat rotation, rotation_;
- vec3 translation, translation_;
-
- std::mutex mtx, mtx2;
-
- QWidget* dst;
-
- QImage front, back;
- QImage image, image2;
-
- struct uv_
- {
- vec2 coords;
- int i;
- };
-
- std::vector<uv_> uv_vec;
-
- std::atomic<bool> fresh;
-
- static constexpr inline int w = 320, h = 240;
-};
-
-class OTR_POSE_WIDGET_EXPORT pose_widget final : public QWidget
+struct OTR_POSE_WIDGET_EXPORT pose_widget final : QWidget
{
public:
- pose_widget(QWidget *parent = nullptr);
- ~pose_widget();
- void rotate_async(double xAngle, double yAngle, double zAngle, double x, double y, double z);
- void rotate_sync(double xAngle, double yAngle, double zAngle, double x, double y, double z);
-
+ explicit pose_widget(QWidget *parent = nullptr);
+ void present(double xAngle, double yAngle, double zAngle, double x, double y, double z);
+ QCheckBox mirror{"Mirror", this};
private:
- pose_transform xform;
- void paintEvent(QPaintEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+ void paintEvent(QPaintEvent*) override;
+
+ Pose_ R, T;
+ QImage front{QImage{":/images/side1.png"}.convertToFormat(QImage::Format_ARGB32)};
+ QImage back {QImage{":/images/side6.png"}.convertToFormat(QImage::Format_ARGB32)
+ .mirrored(true,false)};
+ QImage shine {QImage{front.width(), front.height(), QImage::Format_ARGB32}};
+ QImage shadow{QImage{front.width(), front.height(), QImage::Format_ARGB32}};
};
}
-using pose_widget_impl::pose_widget;
+using pose_widget = pose_widget_impl::pose_widget;
diff --git a/presets/README.txt b/presets/README.txt
new file mode 100644
index 00000000..a89793f8
--- /dev/null
+++ b/presets/README.txt
@@ -0,0 +1,12 @@
+Here you can add presets as .ini files in order to get them copied for
+the end-user as profiles.
+
+Additionally, a preset called 'default.ini' will be sourced into each
+profile the user creates.
+
+This functionality exists for hardware vendors who create their custom
+distributions of opentrack.
+
+The user can delete the profiles in his 'Documents/opentrack-2.3'
+directory. The presets won't be copied again unless their modification
+time changes inside the opentrack's distribution.
diff --git a/proto-flightgear/ftnoir_protocol_fg.cpp b/proto-flightgear/ftnoir_protocol_fg.cpp
index b36a2f47..d777d801 100644
--- a/proto-flightgear/ftnoir_protocol_fg.cpp
+++ b/proto-flightgear/ftnoir_protocol_fg.cpp
@@ -13,7 +13,7 @@
// For Todd and Arda Kutlu
-void flightgear::pose(const double* headpose) {
+void flightgear::pose(const double* headpose, const double*) {
FlightData.x = -headpose[TX] * 1e-2;
FlightData.y = headpose[TY] * 1e-2;
FlightData.z = headpose[TZ] * 1e-2;
@@ -22,7 +22,7 @@ void flightgear::pose(const double* headpose) {
FlightData.r = -headpose[Roll];
FlightData.status = 1;
QHostAddress destIP(quint32(s.ip1 << 24 | s.ip2 << 16 | s.ip3 << 8 | s.ip4));
- (void) outSocket.writeDatagram(reinterpret_cast<const char*>(&FlightData), sizeof(FlightData), destIP, static_cast<quint16>(s.port));
+ (void) outSocket.writeDatagram(reinterpret_cast<const char*>(&FlightData), sizeof(FlightData), destIP, quint16(s.port));
}
module_status flightgear::initialize()
@@ -30,7 +30,7 @@ module_status flightgear::initialize()
if (outSocket.bind(QHostAddress::Any, 0, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint))
return status_ok();
else
- return error(otr_tr("Can't bind to [%1.%2.%3.%4]:%5")
+ return error(tr("Can't bind to [%1.%2.%3.%4]:%5")
.arg(s.ip1).arg(s.ip2).arg(s.ip3).arg(s.ip4)
.arg(s.port));
}
diff --git a/proto-flightgear/ftnoir_protocol_fg.h b/proto-flightgear/ftnoir_protocol_fg.h
index f92c3072..3a08b546 100644
--- a/proto-flightgear/ftnoir_protocol_fg.h
+++ b/proto-flightgear/ftnoir_protocol_fg.h
@@ -15,6 +15,7 @@
#include <QMessageBox>
#include "api/plugin-api.hpp"
#include "options/options.hpp"
+#include "compat/tr.hpp"
using namespace options;
// x,y,z position in meters, heading, pitch and roll in degrees
@@ -38,11 +39,13 @@ struct settings : opts {
{}
};
-class flightgear : public IProtocol
+class flightgear : TR, public IProtocol
{
+ Q_OBJECT
+
public:
- void pose(const double *headpose);
- QString game_name() { return otr_tr("FlightGear"); }
+ void pose(const double *headpose, const double*) override;
+ QString game_name() override { return tr("FlightGear"); }
module_status initialize() override;
private:
settings s;
@@ -56,8 +59,8 @@ class FGControls: public IProtocolDialog
Q_OBJECT
public:
FGControls();
- void register_protocol(IProtocol *) {}
- void unregister_protocol() {}
+ void register_protocol(IProtocol *) override {}
+ void unregister_protocol() override {}
private:
Ui::UICFGControls ui;
settings s;
@@ -68,7 +71,8 @@ private slots:
class flightgearDll : public Metadata
{
-public:
- QString name() { return otr_tr("FlightGear"); }
- QIcon icon() { return QIcon(":/images/flightgear.png"); }
+ Q_OBJECT
+
+ QString name() override { return tr("FlightGear"); }
+ QIcon icon() override { return QIcon(":/images/flightgear.png"); }
};
diff --git a/proto-flightgear/ftnoir_protocol_fg_dialog.cpp b/proto-flightgear/ftnoir_protocol_fg_dialog.cpp
index f6f8e00d..d2098577 100644
--- a/proto-flightgear/ftnoir_protocol_fg_dialog.cpp
+++ b/proto-flightgear/ftnoir_protocol_fg_dialog.cpp
@@ -37,10 +37,10 @@ FGControls::FGControls()
connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
connect(ui.buttonBox, &QDialogButtonBox::helpRequested,
- ui.buttonBox, []()
- {
- static const QString contrib_dir = "file:///" + QDir::toNativeSeparators(QStringLiteral("%1/%2/%3").
- arg(OPENTRACK_BASE_PATH, OPENTRACK_CONTRIB_PATH, "FlightGear"));
+ ui.buttonBox, [] {
+ static const QString contrib_dir =
+ "file:///" + QDir::toNativeSeparators(QStringLiteral("%1/%2/%3").
+ arg(OPENTRACK_BASE_PATH, OPENTRACK_CONTRIB_PATH, "FlightGear"));
QDesktopServices::openUrl(contrib_dir);
});
}
diff --git a/proto-flightgear/lang/de_DE.ts b/proto-flightgear/lang/de_DE.ts
new file mode 100644
index 00000000..436087bc
--- /dev/null
+++ b/proto-flightgear/lang/de_DE.ts
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>UICFGControls</name>
+ <message>
+ <source>FlightGear protocol settings</source>
+ <translation>FlightGear-Protokolleinstellungen</translation>
+ </message>
+ <message>
+ <source>IP address</source>
+ <translation>IP-Adresse</translation>
+ </message>
+ <message>
+ <source>Port</source>
+ <translation>Port</translation>
+ </message>
+</context>
+<context>
+ <name>flightgear</name>
+ <message>
+ <source>Can&apos;t bind to [%1.%2.%3.%4]:%5</source>
+ <translation>Konnte nicht an [%1.%2.%3.%4]:%5 binden</translation>
+ </message>
+ <message>
+ <source>FlightGear</source>
+ <translation>FlightGear</translation>
+ </message>
+</context>
+<context>
+ <name>flightgearDll</name>
+ <message>
+ <source>FlightGear</source>
+ <translation>FlightGear</translation>
+ </message>
+</context>
+</TS>
diff --git a/proto-flightgear/lang/nl_NL.ts b/proto-flightgear/lang/nl_NL.ts
index 18ffbe20..d8c3d3ec 100644
--- a/proto-flightgear/lang/nl_NL.ts
+++ b/proto-flightgear/lang/nl_NL.ts
@@ -16,4 +16,22 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>flightgear</name>
+ <message>
+ <source>Can&apos;t bind to [%1.%2.%3.%4]:%5</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FlightGear</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>flightgearDll</name>
+ <message>
+ <source>FlightGear</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/proto-flightgear/lang/ru_RU.ts b/proto-flightgear/lang/ru_RU.ts
index b97f008d..c204410d 100644
--- a/proto-flightgear/lang/ru_RU.ts
+++ b/proto-flightgear/lang/ru_RU.ts
@@ -16,4 +16,22 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>flightgear</name>
+ <message>
+ <source>Can&apos;t bind to [%1.%2.%3.%4]:%5</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FlightGear</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>flightgearDll</name>
+ <message>
+ <source>FlightGear</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/proto-flightgear/lang/stub.ts b/proto-flightgear/lang/stub.ts
index 688104c8..dc093c57 100644
--- a/proto-flightgear/lang/stub.ts
+++ b/proto-flightgear/lang/stub.ts
@@ -16,4 +16,22 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>flightgear</name>
+ <message>
+ <source>Can&apos;t bind to [%1.%2.%3.%4]:%5</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FlightGear</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>flightgearDll</name>
+ <message>
+ <source>FlightGear</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/proto-flightgear/lang/zh_CN.ts b/proto-flightgear/lang/zh_CN.ts
index 688104c8..ba70f8a0 100644
--- a/proto-flightgear/lang/zh_CN.ts
+++ b/proto-flightgear/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>UICFGControls</name>
<message>
@@ -16,4 +16,22 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>flightgear</name>
+ <message>
+ <source>Can&apos;t bind to [%1.%2.%3.%4]:%5</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FlightGear</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>flightgearDll</name>
+ <message>
+ <source>FlightGear</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/proto-fsuipc/CMakeLists.txt b/proto-fsuipc/CMakeLists.txt
index 3ba2c90c..9b7bd77e 100644
--- a/proto-fsuipc/CMakeLists.txt
+++ b/proto-fsuipc/CMakeLists.txt
@@ -1,12 +1,11 @@
-if(WIN32)
+if(WIN32 AND NOT opentrack-64bit AND opentrack-intel)
set(SDK_FSUIPC "" CACHE PATH "FSUIPC for older MS FSX path")
if(SDK_FSUIPC)
otr_module(proto-fsuipc)
target_link_libraries(opentrack-proto-fsuipc ${SDK_FSUIPC}/FSUIPC_User.lib)
target_include_directories(opentrack-proto-fsuipc SYSTEM PUBLIC ${SDK_FSUIPC})
if(MSVC)
- set_property(TARGET opentrack-proto-fsuipc APPEND_STRING PROPERTY
- LINK_FLAGS "/NODEFAULTLIB:LIBC.lib ")
+ target_link_options(${self} PRIVATE -NODEFAULTLIB:LIBC.lib)
endif()
endif()
endif()
diff --git a/proto-fsuipc/ftnoir_fsuipccontrols.ui b/proto-fsuipc/ftnoir_fsuipccontrols.ui
index 4c85c91c..5a28aad2 100644
--- a/proto-fsuipc/ftnoir_fsuipccontrols.ui
+++ b/proto-fsuipc/ftnoir_fsuipccontrols.ui
@@ -9,12 +9,12 @@
<rect>
<x>0</x>
<y>0</y>
- <width>512</width>
- <height>100</height>
+ <width>248</width>
+ <height>34</height>
</rect>
</property>
<property name="windowTitle">
- <string>FSUIPC settings FaceTrackNoIR</string>
+ <string>FSUIPC</string>
</property>
<property name="windowIcon">
<iconset>
@@ -26,99 +26,11 @@
<property name="autoFillBackground">
<bool>false</bool>
</property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="2" column="1">
- <widget class="QPushButton" name="btnCancel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>100</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string>Cancel</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="txtLocationOfDLL">
- <property name="minimumSize">
- <size>
- <width>230</width>
- <height>0</height>
- </size>
- </property>
- <property name="toolTip">
- <string>Location of FSUIPC.dll</string>
- </property>
- <property name="frameShape">
- <enum>QFrame::Box</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Sunken</enum>
- </property>
- <property name="lineWidth">
- <number>1</number>
- </property>
- <property name="text">
- <string>Location of FSUIPC.dll</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0" colspan="2">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>The DLL should be located in the Modules/ directory of MS FS 2004</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QPushButton" name="btnOK">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>100</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string>OK</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QPushButton" name="btnFindDLL">
- <property name="maximumSize">
- <size>
- <width>35</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string>...</string>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item alignment="Qt::AlignTop">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
diff --git a/proto-fsuipc/ftnoir_protocol_fsuipc.cpp b/proto-fsuipc/ftnoir_protocol_fsuipc.cpp
index 64a14f5f..b7c39ab1 100644
--- a/proto-fsuipc/ftnoir_protocol_fsuipc.cpp
+++ b/proto-fsuipc/ftnoir_protocol_fsuipc.cpp
@@ -13,24 +13,15 @@
#include <cmath>
-fsuipc::fsuipc()
-{
- prevPosX = 0.0f;
- prevPosY = 0.0f;
- prevPosZ = 0.0f;
- prevRotX = 0.0f;
- prevRotY = 0.0f;
- prevRotZ = 0.0f;
-}
+fsuipc::fsuipc() = default;
fsuipc::~fsuipc()
{
FSUIPC_Close();
- FSUIPCLib.unload();
}
template<typename t>
-int fsuipc::scale2AnalogLimits(t x, t min_x, t max_x)
+int fsuipc::scale(t x, t min_x, t max_x)
{
t local_x = x;
@@ -48,58 +39,44 @@ int fsuipc::scale2AnalogLimits(t x, t min_x, t max_x)
return (int) y;
}
+#if 0
template<typename t>
-static inline bool check_float_fresh(t x, t y)
+static bool check_float_fresh(t x, t y)
{
constexpr t eps = t(1e-4);
return std::fabs(x - y) >= eps;
}
+#endif
-void fsuipc::pose(const double *headpose ) {
+void fsuipc::pose(const double *headpose, const double*)
+{
DWORD result;
- TFSState pitch;
- TFSState yaw;
- TFSState roll;
WORD FSZoom;
- double virtPosX;
- double virtPosY;
- double virtPosZ;
-
- double virtRotX;
- double virtRotY;
- double virtRotZ;
-
- // qDebug() << "FSUIPCServer::run() says: started!";
-
- virtRotX = -headpose[Pitch]; // degrees
- virtRotY = headpose[Yaw];
- virtRotZ = headpose[Roll];
-
- virtPosX = 0.0f; // cm, X and Y are not working for FS2002/2004!
- virtPosY = 0.0f;
- virtPosZ = headpose[TZ];
+ // cm, X and Y are not working for FS2002/2004!
+ double pos_z = headpose[TZ];
+ state pitch, yaw, roll; // NOLINT(cppcoreguidelines-pro-type-member-init)
- //
- // Init. the FSUIPC offsets (derived from Free-track...)
- //
+ // offsets derived from freetrack
pitch.Control = 66503;
+ pitch.Value = scale(-headpose[Pitch], -180., 180.); // degrees
+
yaw.Control = 66504;
+ yaw.Value = scale(headpose[Yaw], -180., 180.);
+
roll.Control = 66505;
+ roll.Value = scale(headpose[Roll], -180., 180.);
- //
+#if 0
// Only do this when the data has changed. This way, the HAT-switch can be used when tracking is OFF.
- //
if (check_float_fresh(prevRotX, virtRotX) ||
check_float_fresh(prevRotY, virtRotY) ||
check_float_fresh(prevRotZ, virtRotZ) ||
check_float_fresh(prevPosX, virtPosX) ||
check_float_fresh(prevPosY, virtPosY) ||
check_float_fresh(prevPosZ, virtPosZ))
+#endif
{
- //
- // Open the connection
- //
FSUIPC_Open(SIM_ANY, &result);
//
@@ -110,16 +87,11 @@ void fsuipc::pose(const double *headpose ) {
{
// Write the 4! DOF-data to FS. Only rotations and zoom are possible.
- pitch.Value = scale2AnalogLimits(virtRotX, -180., 180.);
FSUIPC_Write(0x3110, 8, &pitch, &result);
-
- yaw.Value = scale2AnalogLimits(virtRotY, -180., 180.);
FSUIPC_Write(0x3110, 8, &yaw, &result);
-
- roll.Value = scale2AnalogLimits(virtRotZ, -180., 180.);
FSUIPC_Write(0x3110, 8, &roll, &result);
- FSZoom = WORD(virtPosZ + 64);
+ FSZoom = WORD(pos_z + 64);
FSUIPC_Write(0x832E, 2, &FSZoom, &result);
//
@@ -136,23 +108,29 @@ void fsuipc::pose(const double *headpose ) {
}
}
+#if 0
prevPosX = virtPosX;
prevPosY = virtPosY;
prevPosZ = virtPosZ;
prevRotX = virtRotX;
prevRotY = virtRotY;
prevRotZ = virtRotZ;
+#endif
}
module_status fsuipc::initialize()
{
+#if 0
FSUIPCLib.setFileName( s.LocationOfDLL );
FSUIPCLib.setLoadHints(QLibrary::PreventUnloadHint);
- if (FSUIPCLib.load() != true)
- return error(_("Can't load fsuipc at '%1'").arg(s.LocationOfDLL));
+ if (!FSUIPCLib.load())
+ return error(tr("Can't load fsuipc at '%1'").arg(s.LocationOfDLL));
else
return status_ok();
+#else
+ return {};
+#endif
}
OPENTRACK_DECLARE_PROTOCOL(fsuipc, FSUIPCControls, fsuipcDll)
diff --git a/proto-fsuipc/ftnoir_protocol_fsuipc.h b/proto-fsuipc/ftnoir_protocol_fsuipc.h
index c0560efe..99701d15 100644
--- a/proto-fsuipc/ftnoir_protocol_fsuipc.h
+++ b/proto-fsuipc/ftnoir_protocol_fsuipc.h
@@ -27,6 +27,7 @@
#include "options/options.hpp"
using namespace options;
+#if 0
#define FSUIPC_FILENAME "C:\\Program Files\\Microsoft Games\\Flight Simulator 9\\Modules\\FSUIPC.dll"
struct settings : opts {
@@ -36,30 +37,37 @@ struct settings : opts {
LocationOfDLL(b, "dll-location", FSUIPC_FILENAME)
{}
};
+#endif
#pragma pack(push,1) // All fields in structure must be unaligned
-typedef struct
+struct state
{
int Control; // Control identifier
int Value; // Value of DOF
-} TFSState;
+};
#pragma pack(pop)
-class fsuipc : public IProtocol
+class fsuipc : public TR, public IProtocol
{
+ Q_OBJECT
+
public:
fsuipc();
~fsuipc() override;
module_status initialize() override;
- void pose(const double* headpose);
- QString game_name() { return otr_tr("Microsoft Flight Simulator X"); }
+ void pose(const double* headpose, const double*) override;
+ QString game_name() override { return tr("Microsoft Flight Simulator X"); }
private:
+#if 0
QLibrary FSUIPCLib;
- double prevPosX, prevPosY, prevPosZ, prevRotX, prevRotY, prevRotZ;
+
+ double prevPosX = 0, prevPosY = 0, prevPosZ = 0,
+ prevRotX = 0, prevRotY = 0, prevRotZ = 0;
settings s;
+#endif
template<typename t>
- static int scale2AnalogLimits(t x, t min_x, t max_x );
+ static int scale(t x, t min_x, t max_x);
};
class FSUIPCControls: public IProtocolDialog
@@ -67,22 +75,27 @@ class FSUIPCControls: public IProtocolDialog
Q_OBJECT
public:
FSUIPCControls();
- void register_protocol(IProtocol *) {}
- void unregister_protocol() {}
+ void register_protocol(IProtocol *) override {}
+ void unregister_protocol() override {}
private:
Ui::UICFSUIPCControls ui;
+#if 0
settings s;
+#endif
private slots:
- void doOK();
void doCancel();
+#if 0
+ void doOK();
void getLocationOfDLL();
+#endif
};
class fsuipcDll : public Metadata
{
-public:
- QString name() { return otr_tr("FSUIPC -- Microsoft FS2002/FS2004"); }
- QIcon icon() { return QIcon(":/images/fs9.png"); }
+ Q_OBJECT
+
+ QString name() override { return tr("FSUIPC -- Microsoft FS2002/FS2004"); }
+ QIcon icon() override { return QIcon(":/images/fs9.png"); }
};
diff --git a/proto-fsuipc/ftnoir_protocol_fsuipc_dialog.cpp b/proto-fsuipc/ftnoir_protocol_fsuipc_dialog.cpp
index 07507cfb..122c9383 100644
--- a/proto-fsuipc/ftnoir_protocol_fsuipc_dialog.cpp
+++ b/proto-fsuipc/ftnoir_protocol_fsuipc_dialog.cpp
@@ -14,23 +14,30 @@
FSUIPCControls::FSUIPCControls()
{
ui.setupUi( this );
+#if 0
connect(ui.btnOK, SIGNAL(clicked()), this, SLOT(doOK()));
connect(ui.btnCancel, SIGNAL(clicked()), this, SLOT(doCancel()));
connect(ui.btnFindDLL, SIGNAL(clicked()), this, SLOT(getLocationOfDLL()));
-
tie_setting(s.LocationOfDLL, ui.txtLocationOfDLL);
+#else
+ connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &FSUIPCControls::doCancel);
+#endif
}
-void FSUIPCControls::doOK() {
+#if 0
+void FSUIPCControls::doOK()
+{
s.b->save();
close();
}
+#endif
void FSUIPCControls::doCancel()
{
close();
}
+#if 0
void FSUIPCControls::getLocationOfDLL()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Locate file"),
@@ -40,4 +47,4 @@ void FSUIPCControls::getLocationOfDLL()
s.LocationOfDLL = fileName;
}
}
-
+#endif
diff --git a/proto-fsuipc/lang/nl_NL.ts b/proto-fsuipc/lang/nl_NL.ts
index 932f407d..4a7578e9 100644
--- a/proto-fsuipc/lang/nl_NL.ts
+++ b/proto-fsuipc/lang/nl_NL.ts
@@ -15,27 +15,25 @@
<context>
<name>UICFSUIPCControls</name>
<message>
- <source>FSUIPC settings FaceTrackNoIR</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Cancel</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Location of FSUIPC.dll</source>
+ <source>FSUIPC</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>fsuipc</name>
<message>
- <source>The DLL should be located in the Modules/ directory of MS FS 2004</source>
+ <source>Microsoft Flight Simulator X</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>OK</source>
+ <source>Can&apos;t load fsuipc at &apos;%1&apos;</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>fsuipcDll</name>
<message>
- <source>...</source>
+ <source>FSUIPC -- Microsoft FS2002/FS2004</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-fsuipc/lang/ru_RU.ts b/proto-fsuipc/lang/ru_RU.ts
index 77d8c0b3..dd26dd22 100644
--- a/proto-fsuipc/lang/ru_RU.ts
+++ b/proto-fsuipc/lang/ru_RU.ts
@@ -15,27 +15,25 @@
<context>
<name>UICFSUIPCControls</name>
<message>
- <source>FSUIPC settings FaceTrackNoIR</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Cancel</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Location of FSUIPC.dll</source>
+ <source>FSUIPC</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>fsuipc</name>
<message>
- <source>The DLL should be located in the Modules/ directory of MS FS 2004</source>
+ <source>Microsoft Flight Simulator X</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>OK</source>
+ <source>Can&apos;t load fsuipc at &apos;%1&apos;</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>fsuipcDll</name>
<message>
- <source>...</source>
+ <source>FSUIPC -- Microsoft FS2002/FS2004</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-fsuipc/lang/stub.ts b/proto-fsuipc/lang/stub.ts
index 05fbe216..031e1916 100644
--- a/proto-fsuipc/lang/stub.ts
+++ b/proto-fsuipc/lang/stub.ts
@@ -15,27 +15,25 @@
<context>
<name>UICFSUIPCControls</name>
<message>
- <source>FSUIPC settings FaceTrackNoIR</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Cancel</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Location of FSUIPC.dll</source>
+ <source>FSUIPC</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>fsuipc</name>
<message>
- <source>The DLL should be located in the Modules/ directory of MS FS 2004</source>
+ <source>Microsoft Flight Simulator X</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>OK</source>
+ <source>Can&apos;t load fsuipc at &apos;%1&apos;</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>fsuipcDll</name>
<message>
- <source>...</source>
+ <source>FSUIPC -- Microsoft FS2002/FS2004</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-fsuipc/lang/zh_CN.ts b/proto-fsuipc/lang/zh_CN.ts
index 05fbe216..8df82996 100644
--- a/proto-fsuipc/lang/zh_CN.ts
+++ b/proto-fsuipc/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>FSUIPCControls</name>
<message>
@@ -15,27 +15,25 @@
<context>
<name>UICFSUIPCControls</name>
<message>
- <source>FSUIPC settings FaceTrackNoIR</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Cancel</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Location of FSUIPC.dll</source>
+ <source>FSUIPC</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>fsuipc</name>
<message>
- <source>The DLL should be located in the Modules/ directory of MS FS 2004</source>
+ <source>Microsoft Flight Simulator X</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>OK</source>
+ <source>Can&apos;t load fsuipc at &apos;%1&apos;</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>fsuipcDll</name>
<message>
- <source>...</source>
+ <source>FSUIPC -- Microsoft FS2002/FS2004</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-ft/ftnoir_ftcontrols.ui b/proto-ft/ftnoir_ftcontrols.ui
index 4ad00514..8edf5a9d 100644
--- a/proto-ft/ftnoir_ftcontrols.ui
+++ b/proto-ft/ftnoir_ftcontrols.ui
@@ -8,17 +8,13 @@
<property name="enabled">
<bool>true</bool>
</property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>571</width>
- <height>0</height>
- </size>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>533</width>
+ <height>326</height>
+ </rect>
</property>
<property name="windowTitle">
<string>freetrack protocol settings</string>
@@ -34,15 +30,15 @@
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_2">
- <item row="2" column="0">
+ <item row="5" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QGroupBox" name="groupBox_4">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="groupBox_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -50,7 +46,7 @@
</sizepolicy>
</property>
<property name="title">
- <string>Repair NPClient location</string>
+ <string>Select interface</string>
</property>
<property name="alignment">
<set>Qt::AlignJustify|Qt::AlignTop</set>
@@ -58,29 +54,33 @@
<property name="flat">
<bool>false</bool>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>9</number>
</property>
<item>
- <widget class="QPushButton" name="bntLocateNPClient">
- <property name="text">
- <string>Locate DLL</string>
- </property>
+ <widget class="QComboBox" name="cbxSelectInterface">
+ <item>
+ <property name="text">
+ <string>Enable both</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Use freetrack, disable TrackIR</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Use TrackIR, disable freetrack</string>
+ </property>
+ </item>
</widget>
</item>
<item>
- <widget class="QLabel" name="label_10">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
+ <widget class="QLabel" name="label_8">
<property name="text">
- <string>Replace the registry entry if you want to use other software with the NPClient protocol and it doesn't work automatically.
-
-Starting tracking will again overwrite the DLL locations.</string>
+ <string>Disable one of the protocols if game is confused by presence of both at the same time.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
@@ -90,38 +90,123 @@ Starting tracking will again overwrite the DLL locations.</string>
</layout>
</widget>
</item>
- <item row="0" column="0">
- <widget class="QGroupBox" name="groupBox_3">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
+ <item row="1" column="0" rowspan="2">
+ <widget class="QGroupBox" name="groupBox_4">
<property name="title">
- <string>Select interface</string>
+ <string>Library location</string>
</property>
<property name="alignment">
<set>Qt::AlignJustify|Qt::AlignTop</set>
</property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <property name="spacing">
- <number>9</number>
- </property>
- <item>
- <widget class="QComboBox" name="cbxSelectInterface"/>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="bntLocateNPClient">
+ <property name="text">
+ <string>Locate DLL</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_10">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Replace the registry entry if you want to use other software with the NPClient protocol and it doesn't work automatically.
+
+Starting tracking will again overwrite the DLL locations.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
- <item>
- <widget class="QLabel" name="label_8">
- <property name="text">
- <string>Disable one of the protocols if game is confused by presence of both at the same time.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
+ <item row="1" column="0">
+ <widget class="QWidget" name="widget2" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="enable_custom_location">
+ <property name="toolTip">
+ <string>Useful for titles like Elite: Dangerous that require the library to reside in a specified location. Use this to avoid relocating your opentrack installation.</string>
+ </property>
+ <property name="text">
+ <string>Custom location</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="custom_location">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="set_custom_location">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Browse...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QWidget" name="widget_2" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="ephemeral_registry_entry">
+ <property name="toolTip">
+ <string>This is useful when you're only evaluating opentrack, and haven't yet decided to use it all the time.</string>
+ </property>
+ <property name="text">
+ <string>Clear location when tracking is stopped</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
</layout>
@@ -129,8 +214,20 @@ Starting tracking will again overwrite the DLL locations.</string>
</item>
</layout>
</widget>
+ <tabstops>
+ <tabstop>cbxSelectInterface</tabstop>
+ <tabstop>bntLocateNPClient</tabstop>
+ <tabstop>enable_custom_location</tabstop>
+ <tabstop>custom_location</tabstop>
+ <tabstop>set_custom_location</tabstop>
+ <tabstop>ephemeral_registry_entry</tabstop>
+ </tabstops>
<resources>
<include location="ft-protocol.qrc"/>
+ <include location="ft-protocol.qrc"/>
+ <include location="ft-protocol.qrc"/>
+ <include location="ft-protocol.qrc"/>
+ <include location="ft-protocol.qrc"/>
</resources>
<connections/>
<slots>
diff --git a/proto-ft/ftnoir_protocol_ft.cpp b/proto-ft/ftnoir_protocol_ft.cpp
index 0c97ffd2..7f710621 100644
--- a/proto-ft/ftnoir_protocol_ft.cpp
+++ b/proto-ft/ftnoir_protocol_ft.cpp
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2018 Stanislaw Halik <sthalik@misaki.pl>
+/* Copyright (c) 2013-2015, 2017 Stanislaw Halik <sthalik@misaki.pl>
* Copyright (c) 2015 Wim Vriend
*
* Permission to use, copy, modify, and/or distribute this software for any
@@ -6,27 +6,39 @@
* copyright notice and this permission notice appear in all copies.
*/
+#include "compat/library-path.hpp"
+
#include "ftnoir_protocol_ft.h"
#include "csv/csv.h"
-#include "compat/library-path.hpp"
+#include <QDir>
-#include <cassert>
#include <cstddef>
-#include <atomic>
#include <cmath>
-#include <cstdlib>
-
#include <windows.h>
-#include <intrin.h>
-static int page_size()
+freetrack::~freetrack()
{
- SYSTEM_INFO system_info;
- GetSystemInfo(&system_info);
- return system_info.dwPageSize;
+ //dummyTrackIR.kill(); dummyTrackIR.waitForFinished();
+ if (s.ephemeral_library_location)
+ {
+ QSettings settings_ft("Freetrack", "FreetrackClient");
+ QSettings settings_npclient("NaturalPoint", "NATURALPOINT\\NPClient Location");
+
+ settings_ft.setValue("Path", "");
+ settings_npclient.setValue("Path", "");
+
+ if (dummyTrackIR.state() == dummyTrackIR.Running)
+ {
+ dummyTrackIR.kill();
+ dummyTrackIR.waitForFinished(100);
+ }
+ }
+ else
+ dummyTrackIR.close();
}
-static_assert(sizeof(LONG) == 4u, "");
+static_assert(sizeof(LONG) == sizeof(std::int32_t));
+static_assert(sizeof(LONG) == 4u);
never_inline void store(float volatile& place, const float value)
{
@@ -34,58 +46,44 @@ never_inline void store(float volatile& place, const float value)
{
float f32;
LONG i32;
- } value_;
+ } value_ {};
value_.f32 = value;
- static_assert(sizeof(value_) == sizeof(float), "");
- static_assert(offsetof(decltype(value_), f32) == offsetof(decltype(value_), i32), "");
+ static_assert(sizeof(value_) == sizeof(float));
+ static_assert(offsetof(decltype(value_), f32) == offsetof(decltype(value_), i32));
(void)InterlockedExchange((LONG volatile*)&place, value_.i32);
}
-template<typename t, typename u>
-force_inline
-std::enable_if_t<(std::is_integral_v<t>) && sizeof(t) == 4>
-store(t volatile& place, u value)
+template<typename t>
+static void store(t volatile& place, t value)
{
- (void)InterlockedExchange((LONG volatile*) &place, value);
+ static_assert(sizeof(t) == 4u);
+ (void)InterlockedExchange((LONG volatile*) &place, (LONG)value);
}
-force_inline std::int32_t load(std::int32_t volatile& place)
+static std::int32_t load(std::int32_t volatile& place)
{
return InterlockedCompareExchange((volatile LONG*) &place, 0, 0);
}
-freetrack::freetrack()
+void freetrack::pose(const double* headpose, const double* raw)
{
-}
+ constexpr double d2r = M_PI/180;
-freetrack::~freetrack()
-{
- if (shm.success())
- {
- store(pMemData->data.DataID, 0);
- store(pMemData->GameID2, -1);
- }
-
- dummyTrackIR.close();
-}
-
-void freetrack::pose(const double* headpose)
-{
- const float yaw = -degrees_to_rads(headpose[Yaw]);
- const float roll = degrees_to_rads(headpose[Roll]);
+ const float yaw = float(-headpose[Yaw] * d2r);
+ const float roll = float(headpose[Roll] * d2r);
const float tx = float(headpose[TX] * 10);
const float ty = float(headpose[TY] * 10);
const float tz = float(headpose[TZ] * 10);
// HACK: Falcon BMS makes a "bump" if pitch is over the value -sh 20170615
- const bool is_crossing_90 = std::fabs(headpose[Pitch] - 90) < 1e-4;
- const float pitch = -degrees_to_rads(is_crossing_90 ? 89.85 : headpose[Pitch]);
+ const bool is_crossing_90 = std::fabs(headpose[Pitch] - 90) < .15;
+ const float pitch = float(-d2r * (is_crossing_90 ? 89.86 : headpose[Pitch]));
- FTHeap* ft = pMemData;
- FTData* data = &ft->data;
+ FTHeap* const ft = pMemData;
+ FTData* const data = &ft->data;
store(data->X, tx);
store(data->Y, ty);
@@ -95,12 +93,14 @@ void freetrack::pose(const double* headpose)
store(data->Pitch, pitch);
store(data->Roll, roll);
- const std::int32_t id = load(ft->GameID);
-
- data_id++;
- data_id %= 128;
+ store(data->RawYaw, float(-raw[Yaw] * d2r));
+ store(data->RawPitch, float(raw[Pitch] * d2r));
+ store(data->RawRoll, float(raw[Roll] * d2r));
+ store(data->RawX, float(raw[TX] * 10));
+ store(data->RawY, float(raw[TY] * 10));
+ store(data->RawZ, float(raw[TZ] * 10));
- store(data->DataID, 60 * 5 + data_id);
+ const std::int32_t id = load(ft->GameID);
if (intGameID != id)
{
@@ -108,45 +108,49 @@ void freetrack::pose(const double* headpose)
union {
unsigned char table[8];
std::int32_t ints[2];
- } t;
+ } t {};
t.ints[0] = 0; t.ints[1] = 0;
(void)CSV::getGameData(id, t.table, gamename);
- static_assert(sizeof(LONG) == 4, "");
- static_assert(sizeof(int) == 4, "");
-
- // memory mappings are page-aligned due to TLB
- if ((std::intptr_t(pMemData) & page_size() - 1) != 0)
- assert(!"proto/freetrack: memory mapping not page aligned");
+ {
+ // FTHeap pMemData happens to be aligned on a page boundary by virtue of
+ // memory mapping usage (MS Windows equivalent of mmap(2)).
+ static_assert((offsetof(FTHeap, table) & (sizeof(LONG)-1)) == 0);
- // no atomic access for `char'
- for (unsigned k = 0; k < 2; k++)
- store(pMemData->table_ints[k], t.ints[k]);
+ for (unsigned k = 0; k < 2; k++)
+ store(pMemData->table_ints[k], t.ints[k]);
+ }
store(ft->GameID2, id);
+ store(data->DataID, 0u);
intGameID = id;
+ if (gamename.isEmpty())
+ gamename = tr("Unknown game");
+
QMutexLocker foo(&game_name_mutex);
connected_game = gamename;
}
+ else
+ (void)InterlockedAdd((LONG volatile*)&data->DataID, 1);
}
-float freetrack::degrees_to_rads(double degrees)
+QString freetrack::game_name()
{
- return float(degrees*M_PI/180);
+ QMutexLocker foo(&game_name_mutex);
+ return connected_game;
}
-void freetrack::start_dummy()
-{
+void freetrack::start_dummy() {
static const QString program = OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH "TrackIR.exe";
dummyTrackIR.setProgram("\"" + program + "\"");
dummyTrackIR.start();
}
-void freetrack::set_protocols(bool ft, bool npclient)
+module_status freetrack::set_protocols()
{
static const QString program_dir = OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH;
@@ -154,44 +158,61 @@ void freetrack::set_protocols(bool ft, bool npclient)
QSettings settings_ft("Freetrack", "FreetrackClient");
QSettings settings_npclient("NaturalPoint", "NATURALPOINT\\NPClient Location");
- if (ft)
- settings_ft.setValue("Path", program_dir);
- else
- settings_ft.setValue("Path", "");
+ QString location = *s.custom_location_pathname;
+
+ bool use_freetrack = true, use_npclient = true;
+ switch (*s.used_interface)
+ {
+ case settings::enable_npclient: use_freetrack = false; break;
+ case settings::enable_freetrack: use_npclient = false; break;
+ case settings::enable_both: use_freetrack = true; use_npclient = true; break;
+ default:
+ return error(tr("proto/freetrack: wrong interface selection '%1'")
+ .arg(*s.used_interface));
+ }
- if (npclient)
- settings_npclient.setValue("Path", program_dir);
+ if (!s.use_custom_location || s.custom_location_pathname->isEmpty() || !QDir{s.custom_location_pathname}.exists())
+ location = program_dir;
else
- settings_npclient.setValue("Path", "");
+ {
+ bool copy = true;
+
+ if (use_npclient && !QFile{location + "/NPClient.dll"}.exists())
+ copy &= QFile::copy(program_dir + "/NPClient.dll", location + "/NPClient.dll");
+ if (use_npclient && !QFile{location + "/NPClient64.dll"}.exists())
+ copy &= QFile::copy(program_dir + "/NPClient64.dll", location + "/NPClient64.dll");
+ if (use_freetrack && !QFile{location + "/freetrackclient.dll"}.exists())
+ copy &= QFile::copy(program_dir + "/freetrackclient.dll", location + "/freetrackclient.dll");
+ if (use_freetrack && !QFile{location + "/freetrackclient64.dll"}.exists())
+ copy &= QFile::copy(program_dir + "/freetrackclient64.dll", location + "/freetrackclient64.dll");
+
+ if (!copy)
+ return {tr("Can't copy library to selected custom location '%1'").arg(s.custom_location_pathname)};
+ }
+
+ location.replace('\\', '/');
+ if (!location.endsWith('/'))
+ location += '/';
+
+ settings_ft.setValue("Path", use_freetrack ? location : "");
+ settings_npclient.setValue("Path", use_npclient ? location : "");
+
+ return {};
}
module_status freetrack::initialize()
{
if (!shm.success())
- return error(_("Can't load freetrack memory mapping"));
-
- bool use_ft = false, use_npclient = false;
-
- switch (s.intUsedInterface) {
- default:
- case 0:
- use_ft = true;
- use_npclient = true;
- break;
- case 1:
- use_ft = true;
- break;
- case 2:
- use_npclient = true;
- break;
- }
+ return error(tr("Can't load freetrack memory mapping"));
- set_protocols(use_ft, use_npclient);
+ if (auto ret = set_protocols(); !ret.is_ok())
+ return ret;
+ pMemData->data.DataID = 1;
pMemData->data.CamWidth = 100;
pMemData->data.CamHeight = 250;
-#if 1
+#if 0
store(pMemData->data.X1, float(100));
store(pMemData->data.Y1, float(200));
store(pMemData->data.X2, float(300));
@@ -200,14 +221,13 @@ module_status freetrack::initialize()
store(pMemData->data.Y3, float(100));
#endif
- store(pMemData->GameID2, -1);
- store(pMemData->data.DataID, 0);
+ store(pMemData->GameID2, 0);
for (unsigned k = 0; k < 2; k++)
store(pMemData->table_ints[k], 0);
// more games need the dummy executable than previously thought
- if (use_npclient)
+ if (s.used_interface != settings::enable_freetrack)
start_dummy();
return status_ok();
diff --git a/proto-ft/ftnoir_protocol_ft.h b/proto-ft/ftnoir_protocol_ft.h
index 843d1f34..a07747d9 100644
--- a/proto-ft/ftnoir_protocol_ft.h
+++ b/proto-ft/ftnoir_protocol_ft.h
@@ -7,60 +7,61 @@
*/
#pragma once
-
#include "ui_ftnoir_ftcontrols.h"
-
-#include "freetrackclient/fttypes.h"
-
-#include "compat/shm.h"
-#include "options/options.hpp"
#include "api/plugin-api.hpp"
#include <QProcess>
#include <QString>
#include <QMutex>
+#include <QDebug>
+
#include <cinttypes>
+#include "freetrackclient/fttypes.h"
+
+#include "compat/shm.h"
+#include "options/options.hpp"
+
#include <memory>
using namespace options;
struct settings : opts {
- value<int> intUsedInterface;
- settings() :
- opts("proto-freetrack"),
- intUsedInterface(b, "used-interfaces", 0)
- {}
+ enum enable_status : int { enable_both, enable_freetrack, enable_npclient, };
+ value<int> used_interface{b, "used-interfaces", (int)enable_both};
+ value<bool> ephemeral_library_location{b, "ephemeral-library-location", false};
+ value<bool> use_custom_location{b, "use-custom-location", false};
+ value<QString> custom_location_pathname{b, "custom-library-location", {}};
+ settings() : opts("proto-freetrack") {}
};
-class freetrack : public IProtocol
+class freetrack : TR, public IProtocol
{
+ Q_OBJECT
+
public:
- freetrack();
+ freetrack() = default;
~freetrack() override;
module_status initialize() override;
- void pose( const double *headpose );
- QString game_name() override {
- QMutexLocker foo(&game_name_mutex);
- return connected_game;
- }
+ void pose(const double* pose, const double*) override;
+ QString game_name() override;
private:
settings s;
shm_wrapper shm { FREETRACK_HEAP, FREETRACK_MUTEX, sizeof(FTHeap) };
- FTHeap *pMemData { (FTHeap*) shm.ptr() };
+ FTHeap* pMemData { (FTHeap*) shm.ptr() };
QProcess dummyTrackIR;
int intGameID = -1;
QString connected_game;
QMutex game_name_mutex;
- unsigned data_id = 0;
void start_dummy();
- static float degrees_to_rads(double degrees);
public:
- static void set_protocols(bool ft, bool npclient);
+ enum class status { starting, stopping };
+ [[nodiscard]] module_status set_protocols();
+ void clear_protocols();
};
class FTControls: public IProtocolDialog
@@ -77,11 +78,14 @@ private slots:
void selectDLL();
void doOK();
void doCancel();
+ void set_custom_location();
};
class freetrackDll : public Metadata
{
+ Q_OBJECT
+
public:
- QString name() { return otr_tr("freetrack 2.0 Enhanced"); }
+ QString name() { return tr("freetrack 2.0 Enhanced"); }
QIcon icon() { return QIcon(":/images/freetrack.png"); }
};
diff --git a/proto-ft/ftnoir_protocol_ft_dialog.cpp b/proto-ft/ftnoir_protocol_ft_dialog.cpp
index df2878ec..90ff059a 100644
--- a/proto-ft/ftnoir_protocol_ft_dialog.cpp
+++ b/proto-ft/ftnoir_protocol_ft_dialog.cpp
@@ -8,11 +8,11 @@
* purpose with or without fee is hereby granted, provided that the above *
* copyright notice and this permission notice appear in all copies. *
*/
-#include "ftnoir_protocol_ft.h"
-#include "compat/library-path.hpp"
+#include "compat/library-path.hpp"
+#include "ftnoir_protocol_ft.h"
+#include <QDebug>
#include <QFileDialog>
-#include <QFileInfo>
FTControls::FTControls()
{
@@ -22,11 +22,12 @@ FTControls::FTControls()
connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
connect(ui.bntLocateNPClient, SIGNAL(clicked()), this, SLOT(selectDLL()));
- ui.cbxSelectInterface->addItem("Enable both");
- ui.cbxSelectInterface->addItem("Use FreeTrack, hide TrackIR");
- ui.cbxSelectInterface->addItem("Use TrackIR, hide FreeTrack");
+ tie_setting(s.used_interface, ui.cbxSelectInterface);
+ tie_setting(s.ephemeral_library_location, ui.ephemeral_registry_entry);
+ tie_setting(s.custom_location_pathname, ui.custom_location);
+ tie_setting(s.use_custom_location, ui.enable_custom_location);
- tie_setting(s.intUsedInterface, ui.cbxSelectInterface);
+ connect(ui.set_custom_location, &QAbstractButton::clicked, this, &FTControls::set_custom_location);
}
void FTControls::doOK()
@@ -54,4 +55,16 @@ void FTControls::selectDLL()
node.setValue("Path", dllname.dir().path());
}
}
-
+void FTControls::set_custom_location()
+{
+ static const auto program_directory = OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH;
+ auto previous_location = *s.custom_location_pathname;
+ if (!s.use_custom_location || previous_location.isEmpty() || !QDir{previous_location}.exists())
+ previous_location = program_directory;
+ auto dir = QFileDialog::getExistingDirectory(this, tr("Select library location"), previous_location);
+ if (dir.isEmpty() || !QDir{dir}.exists())
+ dir = QString{};
+ else
+ ui.enable_custom_location->setEnabled(true);
+ ui.custom_location->setText(dir);
+}
diff --git a/proto-ft/lang/nl_NL.ts b/proto-ft/lang/nl_NL.ts
index 5f68fb99..c8603973 100644
--- a/proto-ft/lang/nl_NL.ts
+++ b/proto-ft/lang/nl_NL.ts
@@ -11,6 +11,10 @@
<source>Dll file (*.dll);;All Files (*)</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Select library location</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>UICFTControls</name>
@@ -27,10 +31,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Repair NPClient location</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Locate DLL</source>
<translation type="unfinished"></translation>
</message>
@@ -40,5 +40,67 @@
Starting tracking will again overwrite the DLL locations.</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Enable both</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use freetrack, disable TrackIR</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use TrackIR, disable freetrack</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Library location</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Custom location</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Browse...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>This is useful when you&apos;re only evaluating opentrack, and haven&apos;t yet decided to use it all the time.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clear location when tracking is stopped</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Useful for titles like Elite: Dangerous that require the library to reside in a specified location. Use this to avoid relocating your opentrack installation.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>freetrack</name>
+ <message>
+ <source>Unknown game</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t load freetrack memory mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t copy library to selected custom location &apos;%1&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>proto/freetrack: wrong interface selection &apos;%1&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>freetrackDll</name>
+ <message>
+ <source>freetrack 2.0 Enhanced</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
</TS>
diff --git a/proto-ft/lang/ru_RU.ts b/proto-ft/lang/ru_RU.ts
index 31d0a2be..a6433fd5 100644
--- a/proto-ft/lang/ru_RU.ts
+++ b/proto-ft/lang/ru_RU.ts
@@ -11,6 +11,10 @@
<source>Dll file (*.dll);;All Files (*)</source>
<translation></translation>
</message>
+ <message>
+ <source>Select library location</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>UICFTControls</name>
@@ -27,10 +31,6 @@
<translation>Отключите один из протоколов в Ñлучае, еÑли при включении обоих интерфейÑов игра не корректно определÑет их.</translation>
</message>
<message>
- <source>Repair NPClient location</source>
- <translation>Решение проблем Ñ Ñ€Ð°Ñположением NPClient&apos;а </translation>
- </message>
- <message>
<source>Locate DLL</source>
<translation>Укажите DLL</translation>
</message>
@@ -42,5 +42,67 @@ Starting tracking will again overwrite the DLL locations.</source>
ЗапуÑк отÑÐ»ÐµÐ¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð² opentrack приведет к перезапиÑи раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ DLL-файла.</translation>
</message>
+ <message>
+ <source>Enable both</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use freetrack, disable TrackIR</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use TrackIR, disable freetrack</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Library location</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Custom location</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Browse...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>This is useful when you&apos;re only evaluating opentrack, and haven&apos;t yet decided to use it all the time.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clear location when tracking is stopped</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Useful for titles like Elite: Dangerous that require the library to reside in a specified location. Use this to avoid relocating your opentrack installation.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>freetrack</name>
+ <message>
+ <source>Unknown game</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t load freetrack memory mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t copy library to selected custom location &apos;%1&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>proto/freetrack: wrong interface selection &apos;%1&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>freetrackDll</name>
+ <message>
+ <source>freetrack 2.0 Enhanced</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
</TS>
diff --git a/proto-ft/lang/stub.ts b/proto-ft/lang/stub.ts
index 4fb52c8c..ac88d895 100644
--- a/proto-ft/lang/stub.ts
+++ b/proto-ft/lang/stub.ts
@@ -11,6 +11,10 @@
<source>Dll file (*.dll);;All Files (*)</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Select library location</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>UICFTControls</name>
@@ -27,10 +31,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Repair NPClient location</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Locate DLL</source>
<translation type="unfinished"></translation>
</message>
@@ -40,5 +40,67 @@
Starting tracking will again overwrite the DLL locations.</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Enable both</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use freetrack, disable TrackIR</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use TrackIR, disable freetrack</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Library location</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Custom location</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Browse...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>This is useful when you&apos;re only evaluating opentrack, and haven&apos;t yet decided to use it all the time.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clear location when tracking is stopped</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Useful for titles like Elite: Dangerous that require the library to reside in a specified location. Use this to avoid relocating your opentrack installation.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>freetrack</name>
+ <message>
+ <source>Unknown game</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t load freetrack memory mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t copy library to selected custom location &apos;%1&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>proto/freetrack: wrong interface selection &apos;%1&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>freetrackDll</name>
+ <message>
+ <source>freetrack 2.0 Enhanced</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
</TS>
diff --git a/proto-ft/lang/zh_CN.ts b/proto-ft/lang/zh_CN.ts
index 63791bc8..7706d27c 100644
--- a/proto-ft/lang/zh_CN.ts
+++ b/proto-ft/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>FTControls</name>
<message>
@@ -11,6 +11,10 @@
<source>Dll file (*.dll);;All Files (*)</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Select library location</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>UICFTControls</name>
@@ -19,10 +23,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Repair NPClient location</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Locate DLL</source>
<translation type="unfinished"></translation>
</message>
@@ -40,5 +40,67 @@ Starting tracking will again overwrite the DLL locations.</source>
<source>Disable one of the protocols if game is confused by presence of both at the same time.</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Enable both</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use freetrack, disable TrackIR</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use TrackIR, disable freetrack</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Library location</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Custom location</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Browse...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>This is useful when you&apos;re only evaluating opentrack, and haven&apos;t yet decided to use it all the time.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clear location when tracking is stopped</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Useful for titles like Elite: Dangerous that require the library to reside in a specified location. Use this to avoid relocating your opentrack installation.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>freetrack</name>
+ <message>
+ <source>Unknown game</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t load freetrack memory mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t copy library to selected custom location &apos;%1&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>proto/freetrack: wrong interface selection &apos;%1&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>freetrackDll</name>
+ <message>
+ <source>freetrack 2.0 Enhanced</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
</TS>
diff --git a/proto-iokit-foohid/CMakeLists.txt b/proto-iokit-foohid/CMakeLists.txt
index cdc8fb69..6a4bf89a 100644
--- a/proto-iokit-foohid/CMakeLists.txt
+++ b/proto-iokit-foohid/CMakeLists.txt
@@ -1,4 +1,4 @@
-IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+if(APPLE)
otr_module(proto-iokit-foohid)
- set_property(TARGET opentrack-proto-iokit-foohid APPEND_STRING PROPERTY LINK_FLAGS "-framework IOKit ")
+ target_link_options(${self} PRIVATE -framework IOKit)
endif()
diff --git a/proto-iokit-foohid/foohidjoystick.cpp b/proto-iokit-foohid/foohidjoystick.cpp
index 0b305d08..b4964bb7 100644
--- a/proto-iokit-foohid/foohidjoystick.cpp
+++ b/proto-iokit-foohid/foohidjoystick.cpp
@@ -7,8 +7,6 @@
#include "foohidjoystick.h"
-#include "compat/macros.hpp"
-
const char FOOHID_SERVICE_NAME[] = "it_unbit_foohid";
enum class FooHIDMethod {
@@ -51,7 +49,7 @@ static bool connectToService(io_connect_t *connection, QString *errorMessage)
IOServiceMatching(FOOHID_SERVICE_NAME),
&iterator);
if (ret != KERN_SUCCESS) {
- *errorMessage = otr_tr("Unable to find FooHID IOService.");
+ *errorMessage = QObject::tr("Unable to find FooHID IOService.");
return false;
}
// Iterate over services and try to open connection
@@ -66,7 +64,7 @@ static bool connectToService(io_connect_t *connection, QString *errorMessage)
}
IOObjectRelease(iterator);
if (!found) {
- *errorMessage = otr_tr("Unable to connect to FooHID IOService.");
+ *errorMessage = QObject::tr("Unable to connect to FooHID IOService.");
return false;
}
return true;
@@ -89,7 +87,7 @@ FooHIDJoystick::FooHIDJoystick(const QByteArray &name, const QByteArray &serialN
deviceCreated = createDevice();
_hasError = !deviceCreated;
if (!deviceCreated)
- _errorMessage = otr_tr("Failed to create virtual joystick");
+ _errorMessage = tr("Failed to create virtual joystick");
}
}
@@ -116,7 +114,7 @@ void FooHIDJoystick::setValue(JoystickValues newValues)
values = newValues;
if (!sendToDevice()) {
_hasError = true;
- _errorMessage = otr_tr("Failed to send values to virtual joystick");
+ _errorMessage = tr("Failed to send values to virtual joystick");
}
}
diff --git a/proto-iokit-foohid/foohidjoystick.h b/proto-iokit-foohid/foohidjoystick.h
index a1f74304..e987c229 100644
--- a/proto-iokit-foohid/foohidjoystick.h
+++ b/proto-iokit-foohid/foohidjoystick.h
@@ -9,6 +9,7 @@
#include <QByteArray>
#include <QString>
+#include <QCoreApplication>
#include <IOKit/IOKitLib.h>
@@ -23,6 +24,7 @@ struct JoystickValues {
class FooHIDJoystick
{
+ Q_DECLARE_TR_FUNCTIONS(FooHIDJoystick)
public:
FooHIDJoystick(const QByteArray &name, const QByteArray &serialNumber);
~FooHIDJoystick();
diff --git a/proto-iokit-foohid/iokitprotocol.cpp b/proto-iokit-foohid/iokitprotocol.cpp
index b66aeb7b..a364fd8a 100644
--- a/proto-iokit-foohid/iokitprotocol.cpp
+++ b/proto-iokit-foohid/iokitprotocol.cpp
@@ -10,8 +10,6 @@
#include "foohidjoystick.h"
#include "iokitprotocoldialog.h"
-#include "compat/macros.hpp"
-
#include <QDebug>
IOKitProtocol::IOKitProtocol()
@@ -25,14 +23,14 @@ IOKitProtocol::IOKitProtocol()
module_status IOKitProtocol::initialize()
{
if (!joystick)
- return otr_tr("Load failure");
+ return tr("Load failure");
if (joystick->hasError())
{
QString msg = joystick->errorMessage();
if (msg.isEmpty())
- msg = otr_tr("Unknown error");
+ msg = tr("Unknown error");
return error(msg);
}
@@ -47,14 +45,14 @@ static uint8_t valueToStick(FooHIDJoystick *stick, double min, double value, dou
stick->minValue() + stick->range()));
}
-void IOKitProtocol::pose(const double *headpose)
+void IOKitProtocol::pose(const double *headpose, const double*)
{
- const uint8_t x = valueToStick(joystick.get(), -75., headpose[0], +75.);
- const uint8_t y = valueToStick(joystick.get(), -75., headpose[1], +75.);
- const uint8_t z = valueToStick(joystick.get(), -75., headpose[2], +75.);
- const uint8_t rx = valueToStick(joystick.get(), -180., headpose[3], +180.);
- const uint8_t ry = valueToStick(joystick.get(), -180., headpose[4], +180.);
- const uint8_t rz = valueToStick(joystick.get(), -180., headpose[5], +180.);
+ const uint8_t x = valueToStick(&*joystick, -75., headpose[0], +75.);
+ const uint8_t y = valueToStick(&*joystick, -75., headpose[1], +75.);
+ const uint8_t z = valueToStick(&*joystick, -75., headpose[2], +75.);
+ const uint8_t rx = valueToStick(&*joystick, -180., headpose[3], +180.);
+ const uint8_t ry = valueToStick(&*joystick, -180., headpose[4], +180.);
+ const uint8_t rz = valueToStick(&*joystick, -180., headpose[5], +180.);
joystick->setValue({x, y, z, rx, ry, rz});
}
diff --git a/proto-iokit-foohid/iokitprotocol.h b/proto-iokit-foohid/iokitprotocol.h
index e3a1355c..8148e7ab 100644
--- a/proto-iokit-foohid/iokitprotocol.h
+++ b/proto-iokit-foohid/iokitprotocol.h
@@ -19,7 +19,7 @@ public:
IOKitProtocol();
module_status initialize() override;
- void pose(const double *headpose) final;
+ void pose(const double *headpose, const double*) final;
QString game_name() final;
private:
@@ -28,7 +28,8 @@ private:
class IOKitProtocolMetadata : public Metadata
{
-public:
- QString name() { return otr_tr("Virtual joystick"); }
+ Q_OBJECT
+
+ QString name() { return tr("Virtual joystick"); }
QIcon icon() { return QIcon(":/images/opentrack.png"); }
};
diff --git a/ext-falcon-bms-linear-acc/lang/zh_CN.ts b/proto-iokit-foohid/lang/zh_CN.ts
index 6401616d..e5ca8aa9 100644
--- a/ext-falcon-bms-linear-acc/lang/zh_CN.ts
+++ b/proto-iokit-foohid/lang/zh_CN.ts
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
</TS>
diff --git a/proto-libevdev/ftnoir_libevdev_controls.ui b/proto-libevdev/ftnoir_libevdev_controls.ui
index d2b86445..8aa52627 100644
--- a/proto-libevdev/ftnoir_libevdev_controls.ui
+++ b/proto-libevdev/ftnoir_libevdev_controls.ui
@@ -14,7 +14,7 @@
</rect>
</property>
<property name="windowTitle">
- <string>VJoy</string>
+ <string>libevdev options</string>
</property>
<property name="windowIcon">
<iconset>
diff --git a/proto-libevdev/ftnoir_protocol_libevdev.cpp b/proto-libevdev/ftnoir_protocol_libevdev.cpp
index 6bf9b511..fefcd9bb 100644
--- a/proto-libevdev/ftnoir_protocol_libevdev.cpp
+++ b/proto-libevdev/ftnoir_protocol_libevdev.cpp
@@ -1,36 +1,65 @@
+// strerror_r(3)
+
+#ifdef __clang__
+# pragma clang diagnostic ignored "-Wreserved-id-macro"
+# pragma clang diagnostic ignored "-Wunused-macros"
+#endif
+
+#ifndef _POSIX_C_SOURCE
+# define _POSIX_C_SOURCE 200112L
+#endif
+
#include "ftnoir_protocol_libevdev.h"
#include "api/plugin-api.hpp"
#include "compat/math.hpp"
-#include <stdio.h>
+#include <cstdlib>
+#include <cstring>
+#include <cstdio>
+#include <algorithm>
+
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
-#include <string.h>
-#include <algorithm>
+#include <QDebug>
-#define CHECK_LIBEVDEV(expr) if ((error = (expr)) != 0) goto error;
+#define CHECK_LIBEVDEV(expr) \
+ do { \
+ if (int error = (expr); error != 0) \
+ { \
+ error_code = -error; \
+ error_expr = #expr; \
+ goto fail; \
+ } \
+ } while (false)
-static const int max_input = 65535;
-static const int mid_input = 32767;
-static const int min_input = 0;
+static constexpr int max_input = 1 << 16;
+static constexpr int mid_input = (1 << 15) - 1;
+static constexpr int min_input = 0;
-evdev::evdev() : dev(NULL), uidev(NULL)
+evdev::evdev()
{
- int error = 0;
-
dev = libevdev_new();
if (!dev)
- goto error;
+ {
+ error_code = errno;
+ error_expr = "libevdev_new();";
+ goto fail;
+ }
CHECK_LIBEVDEV(libevdev_enable_property(dev, INPUT_PROP_BUTTONPAD));
libevdev_set_name(dev, "opentrack headpose");
+
+ libevdev_set_id_bustype(dev, 3);
+ libevdev_set_id_vendor(dev, 4324);
+ libevdev_set_id_product(dev, 3798);
+ libevdev_set_id_version(dev, 123);
struct input_absinfo absinfo;
@@ -57,15 +86,17 @@ evdev::evdev() : dev(NULL), uidev(NULL)
CHECK_LIBEVDEV(libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uidev));
return;
-error:
+
+fail:
if (uidev)
libevdev_uinput_destroy(uidev);
if (dev)
libevdev_free(dev);
- if (error)
- fprintf(stderr, "libevdev error: %d\n", error);
- uidev = NULL;
- dev = NULL;
+
+ qDebug() << "libevdev error" << error_code;
+
+ uidev = nullptr;
+ dev = nullptr;
}
evdev::~evdev()
@@ -76,7 +107,7 @@ evdev::~evdev()
libevdev_free(dev);
}
-void evdev::pose(const double* headpose) {
+void evdev::pose(const double* headpose, const double*) {
static const int axes[] = {
/* translation goes first */
ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ
@@ -93,8 +124,8 @@ void evdev::pose(const double* headpose) {
for (int i = 0; i < 6; i++)
{
- int value = headpose[i] * mid_input / max_value[i] + mid_input;
- int normalized = clamp(value, min_input, max_input);
+ int value = (int)(headpose[i] * mid_input / max_value[i] + mid_input);
+ int normalized = std::clamp(value, min_input, max_input);
(void) libevdev_uinput_write_event(uidev, EV_ABS, axes[i], normalized);
}
@@ -103,14 +134,22 @@ void evdev::pose(const double* headpose) {
module_status evdev::initialize()
{
- if (access("/dev/uinput", R_OK | W_OK))
+ if (error_code)
{
+ const char* msg;
char buf[128] {};
- (void) strerror_r(errno, buf, sizeof(buf));
- return error(_("Can't open /dev/uinput: %1").arg(buf));
- }
- return status_ok();
+ if (!(msg = strerror_r(error_code, buf, sizeof(buf))))
+ {
+ snprintf(buf, sizeof(buf), "%d", error_code);
+ msg = buf;
+ }
+
+ return error(QStringLiteral("libevdev call '%1' failed with error '%2'")
+ .arg(!error_expr ? "<NULL>" : error_expr, msg));
+ }
+ else
+ return {};
}
OPENTRACK_DECLARE_PROTOCOL(evdev, LibevdevControls, evdevDll)
diff --git a/proto-libevdev/ftnoir_protocol_libevdev.h b/proto-libevdev/ftnoir_protocol_libevdev.h
index 7cf59053..b81c3baf 100644
--- a/proto-libevdev/ftnoir_protocol_libevdev.h
+++ b/proto-libevdev/ftnoir_protocol_libevdev.h
@@ -7,36 +7,34 @@
#pragma once
#include "ui_ftnoir_libevdev_controls.h"
-#include "compat/macros.hpp"
#include "api/plugin-api.hpp"
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
#include <QMessageBox>
-class evdev : public IProtocol
+class evdev : public TR, public IProtocol
{
+ Q_OBJECT
+
public:
evdev();
~evdev() override;
- bool correct() {
- return dev != NULL;
- }
- void pose(const double *headpose);
- QString game_name() {
- return _("Virtual joystick for Linux");
- }
-
+ void pose(const double *headpose, const double*) override;
+ QString game_name() override { return tr("Virtual joystick for Linux"); }
module_status initialize() override;
private:
- struct libevdev* dev;
- struct libevdev_uinput* uidev;
+ struct libevdev* dev = nullptr;
+ struct libevdev_uinput* uidev = nullptr;
+ int error_code = 0;
+ const char* error_expr = nullptr;
};
class LibevdevControls: public IProtocolDialog
{
Q_OBJECT
+
public:
LibevdevControls();
void register_protocol(IProtocol *) {}
@@ -53,7 +51,8 @@ private slots:
class evdevDll : public Metadata
{
-public:
- QString name() { return _("libevdev joystick receiver"); }
- QIcon icon() { return QIcon(":/images/linux.png"); }
+ Q_OBJECT
+
+ QString name() override { return tr("libevdev joystick receiver"); }
+ QIcon icon() override { return QIcon(":/images/linux.png"); }
};
diff --git a/proto-libevdev/lang/de_DE.ts b/proto-libevdev/lang/de_DE.ts
new file mode 100644
index 00000000..f66a1b39
--- /dev/null
+++ b/proto-libevdev/lang/de_DE.ts
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>UICLibevdevControls</name>
+ <message>
+ <source>libevdev options</source>
+ <translation>libevdev-Optionen</translation>
+ </message>
+ <message>
+ <source>Make sure rw for /dev/input/uinput!</source>
+ <translation>Stelle sicher, dass /dev/input/uinput rw-Berechtigungen hat!</translation>
+ </message>
+ <message>
+ <source>OK</source>
+ <translation>Okay</translation>
+ </message>
+ <message>
+ <source>Cancel</source>
+ <translation>Abbruch</translation>
+ </message>
+</context>
+<context>
+ <name>evdev</name>
+ <message>
+ <source>Virtual joystick for Linux</source>
+ <translation>Virtueller Joystick für Linux</translation>
+ </message>
+</context>
+<context>
+ <name>evdevDll</name>
+ <message>
+ <source>libevdev joystick receiver</source>
+ <translation>libevdev-Joystick-Empfänger</translation>
+ </message>
+</context>
+</TS>
diff --git a/proto-libevdev/lang/nl_NL.ts b/proto-libevdev/lang/nl_NL.ts
index 503c6938..d6a2c333 100644
--- a/proto-libevdev/lang/nl_NL.ts
+++ b/proto-libevdev/lang/nl_NL.ts
@@ -4,24 +4,34 @@
<context>
<name>UICLibevdevControls</name>
<message>
- <location filename="../ftnoir_libevdev_controls.ui" line="+17"/>
- <source>VJoy</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+16"/>
<source>Make sure rw for /dev/input/uinput!</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+32"/>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+25"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>libevdev options</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>evdev</name>
+ <message>
+ <source>Virtual joystick for Linux</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>evdevDll</name>
+ <message>
+ <source>libevdev joystick receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
</TS>
diff --git a/proto-libevdev/lang/ru_RU.ts b/proto-libevdev/lang/ru_RU.ts
index 9e78d930..c6310cd0 100644
--- a/proto-libevdev/lang/ru_RU.ts
+++ b/proto-libevdev/lang/ru_RU.ts
@@ -4,24 +4,34 @@
<context>
<name>UICLibevdevControls</name>
<message>
- <location filename="../ftnoir_libevdev_controls.ui" line="+17"/>
- <source>VJoy</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+16"/>
<source>Make sure rw for /dev/input/uinput!</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+32"/>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+25"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>libevdev options</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>evdev</name>
+ <message>
+ <source>Virtual joystick for Linux</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>evdevDll</name>
+ <message>
+ <source>libevdev joystick receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
</TS>
diff --git a/proto-libevdev/lang/stub.ts b/proto-libevdev/lang/stub.ts
index ae9cf4cc..ffafa2ec 100644
--- a/proto-libevdev/lang/stub.ts
+++ b/proto-libevdev/lang/stub.ts
@@ -4,24 +4,34 @@
<context>
<name>UICLibevdevControls</name>
<message>
- <location filename="../ftnoir_libevdev_controls.ui" line="+17"/>
- <source>VJoy</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+16"/>
<source>Make sure rw for /dev/input/uinput!</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+32"/>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+25"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>libevdev options</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>evdev</name>
+ <message>
+ <source>Virtual joystick for Linux</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>evdevDll</name>
+ <message>
+ <source>libevdev joystick receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
</TS>
diff --git a/filter-kalman/lang/zh_CN.ts b/proto-libevdev/lang/zh_CN.ts
index a572be5f..f5e8694c 100644
--- a/filter-kalman/lang/zh_CN.ts
+++ b/proto-libevdev/lang/zh_CN.ts
@@ -1,30 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
- <name>KalmanUICdialog_kalman</name>
+ <name>UICLibevdevControls</name>
<message>
- <source>Kalman settings</source>
+ <source>Make sure rw for /dev/input/uinput!</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Measurement noise</source>
+ <source>OK</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Rotation</source>
+ <source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Position</source>
+ <source>libevdev options</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>evdev</name>
<message>
- <source>°</source>
+ <source>Virtual joystick for Linux</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>evdevDll</name>
<message>
- <source>-</source>
+ <source>libevdev joystick receiver</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-mouse/ftnoir_mousecontrols.ui b/proto-mouse/ftnoir_mousecontrols.ui
index f970f887..45b33470 100644
--- a/proto-mouse/ftnoir_mousecontrols.ui
+++ b/proto-mouse/ftnoir_mousecontrols.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>UICMOUSEControls</class>
- <widget class="QWidget" name="UICMOUSEControls">
+ <class>UI_mouse</class>
+ <widget class="QWidget" name="UI_mouse">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
@@ -9,70 +9,42 @@
<rect>
<x>0</x>
<y>0</y>
- <width>413</width>
- <height>155</height>
+ <width>360</width>
+ <height>146</height>
</rect>
</property>
<property name="minimumSize">
<size>
- <width>413</width>
+ <width>360</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
- <string>Mouse protocol settings</string>
+ <string>Mouse protocol</string>
</property>
<property name="windowIcon">
<iconset resource="win32-mouse-protocol.qrc">
<normaloff>:/images/mouse.png</normaloff>:/images/mouse.png</iconset>
</property>
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="autoFillBackground">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout">
+ <layout class="QFormLayout" name="formLayout">
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>11</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
<item row="0" column="0">
<widget class="QLabel" name="textLabel2_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Map mouse X to:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="wordWrap">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="textLabel2_3">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
<property name="text">
- <string>Map mouse Y to:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="wordWrap">
- <bool>false</bool>
+ <string>X axis</string>
</property>
</widget>
</item>
- <item row="0" column="1" colspan="2">
- <widget class="QComboBox" name="cbxSelectMouse_X">
+ <item row="0" column="1">
+ <widget class="QComboBox" name="axis_x">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
@@ -81,16 +53,10 @@
</property>
<property name="maximumSize">
<size>
- <width>80</width>
+ <width>105</width>
<height>16777215</height>
</size>
</property>
- <property name="toolTip">
- <string>Select Number</string>
- </property>
- <property name="insertPolicy">
- <enum>QComboBox::InsertAlphabetically</enum>
- </property>
<item>
<property name="text">
<string>None</string>
@@ -128,8 +94,38 @@
</item>
</widget>
</item>
- <item row="1" column="1" colspan="2">
- <widget class="QComboBox" name="cbxSelectMouse_Y">
+ <item row="1" column="0">
+ <widget class="QLabel" name="textLabel2_6">
+ <property name="text">
+ <string>Sensitivity</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSlider" name="sensitivity_x">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickInterval">
+ <number>25</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="textLabel2_3">
+ <property name="text">
+ <string>Y axis</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="axis_y">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
@@ -138,16 +134,10 @@
</property>
<property name="maximumSize">
<size>
- <width>80</width>
+ <width>105</width>
<height>16777215</height>
</size>
</property>
- <property name="toolTip">
- <string>Select Number</string>
- </property>
- <property name="insertPolicy">
- <enum>QComboBox::InsertAlphabetically</enum>
- </property>
<item>
<property name="text">
<string>None</string>
@@ -185,36 +175,17 @@
</item>
</widget>
</item>
- <item row="4" column="1">
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="textLabel2_4">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
+ <item row="3" column="0">
+ <widget class="QLabel" name="textLabel2_7">
<property name="text">
- <string>X axis sensitivity</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="wordWrap">
- <bool>false</bool>
+ <string>Sensitivity</string>
</property>
</widget>
</item>
- <item row="2" column="1">
- <widget class="QSlider" name="sensitivity_x">
+ <item row="3" column="1">
+ <widget class="QSlider" name="sensitivity_y">
<property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@@ -227,38 +198,49 @@
</property>
</widget>
</item>
- <item row="3" column="0">
- <widget class="QLabel" name="textLabel2_5">
+ <item row="4" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Method</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QComboBox" name="input_method">
<property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="text">
- <string>Y axis sensitivity</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="wordWrap">
- <bool>false</bool>
+ <property name="maximumSize">
+ <size>
+ <width>105</width>
+ <height>16777215</height>
+ </size>
</property>
+ <item>
+ <property name="text">
+ <string>Direct input</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Legacy</string>
+ </property>
+ </item>
</widget>
</item>
- <item row="3" column="1">
- <widget class="QSlider" name="sensitivity_y">
+ <item row="5" column="0" colspan="2">
+ <widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="tickInterval">
- <number>25</number>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
@@ -268,9 +250,4 @@
<include location="win32-mouse-protocol.qrc"/>
</resources>
<connections/>
- <slots>
- <slot>startEngineClicked()</slot>
- <slot>stopEngineClicked()</slot>
- <slot>cameraSettingsClicked()</slot>
- </slots>
</ui>
diff --git a/proto-mouse/ftnoir_protocol_mouse.cpp b/proto-mouse/ftnoir_protocol_mouse.cpp
index b4b27ea9..e5b2192b 100644
--- a/proto-mouse/ftnoir_protocol_mouse.cpp
+++ b/proto-mouse/ftnoir_protocol_mouse.cpp
@@ -9,65 +9,83 @@
#include "api/plugin-api.hpp"
#include "compat/math.hpp"
+
#include <cmath>
#include <algorithm>
+
#include <windows.h>
#ifndef MOUSEEVENTF_MOVE_NOCOALESCE
# define MOUSEEVENTF_MOVE_NOCOALESCE 0x2000
#endif
-static const double invert[] =
-{
- 1., 1., 1.,
+static const double invert[] = {
+ 1., 1., 1.,
1., -1., 1.
};
-void mouse::pose(const double *headpose)
+void mouse::pose(const double* headpose, const double*)
{
- const int axis_x = s.Mouse_X - 1;
- const int axis_y = s.Mouse_Y - 1;
+ const int axis_x = s.mouse_x - 1;
+ const int axis_y = s.mouse_y - 1;
int mouse_x = 0, mouse_y = 0;
- if (axis_x >= 0 && axis_x < 6)
- {
+ if (axis_x == std::clamp(axis_x, (int)Axis_MIN, (int)Axis_MAX))
mouse_x = get_value(headpose[axis_x] * invert[axis_x],
- s.sensitivity_x(),
+ *s.sensitivity_x,
axis_x >= 3);
- }
- if (axis_y >= 0 && axis_y < 6)
+ if (axis_y == std::clamp(axis_y, (int)Axis_MIN, (int)Axis_MAX))
mouse_y = get_value(headpose[axis_y] * invert[axis_y],
- s.sensitivity_y(),
+ *s.sensitivity_y,
axis_y >= 3);
- MOUSEINPUT mi;
- mi.dx = get_delta(mouse_x, last_x);
- mi.dy = get_delta(mouse_y, last_y);
- mi.mouseData = 0;
- mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_MOVE_NOCOALESCE;
- mi.time = 0;
- mi.dwExtraInfo = 0;
- INPUT input;
- input.type = INPUT_MOUSE;
- input.mi = mi;
- (void) SendInput(1, &input, sizeof(INPUT));
-
- last_x = mouse_x;
- last_y = mouse_y;
+ int dx = get_delta(mouse_x, last_x), dy = mouse_y - last_y;
+
+ last_x = mouse_x; last_y = mouse_y;
+
+ if (dx || dy)
+ {
+ switch (s.input_method)
+ {
+ default:
+ eval_once(qDebug() << "proto/mouse: invalid input method");
+ [[fallthrough]];
+ case input_direct:
+ {
+ INPUT input;
+ input.type = INPUT_MOUSE;
+ MOUSEINPUT& mi = input.mi;
+ mi = {};
+ mi.dx = dx;
+ mi.dy = dy;
+ mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_MOVE_NOCOALESCE;
+
+ (void)SendInput(1, &input, sizeof(input));
+
+ break;
+ }
+ case input_legacy:
+ {
+ POINT pt{};
+ (void)GetCursorPos(&pt);
+ (void)SetCursorPos(pt.x + dx, pt.y + dy);
+
+ break;
+ }
+ }
+ }
}
QString mouse::game_name()
{
- return otr_tr("Mouse tracker");
+ return tr("Mouse tracker");
}
int mouse::get_delta(int val, int prev)
{
- using std::abs;
-
- const int a = abs(val - prev), b = abs(val + prev);
+ const int a = std::abs(val - prev), b = std::abs(val + prev);
if (b < a)
return val + prev;
else
@@ -76,12 +94,9 @@ int mouse::get_delta(int val, int prev)
int mouse::get_value(double val, double sensitivity, bool is_rotation)
{
- constexpr double sgn[] = { 1e-2, 1 };
- constexpr double c = 1e-1;
+ constexpr double c[] = { 1e-3, 1e-1 };
- return iround(val * c * sensitivity * sgn[unsigned(is_rotation)]);
+ return iround(val * sensitivity * c[unsigned(is_rotation)]);
}
-mouse::mouse() : last_x(0), last_y(0) {}
-
OPENTRACK_DECLARE_PROTOCOL(mouse, MOUSEControls, mouseDll)
diff --git a/proto-mouse/ftnoir_protocol_mouse.h b/proto-mouse/ftnoir_protocol_mouse.h
index 55b26fe5..9b52a053 100644
--- a/proto-mouse/ftnoir_protocol_mouse.h
+++ b/proto-mouse/ftnoir_protocol_mouse.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015 Stanislaw Halik <sthalik@misaki.pl>
+/* Copyright (c) 2015, 2019 Stanislaw Halik <sthalik@misaki.pl>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -10,45 +10,50 @@
#include "ui_ftnoir_mousecontrols.h"
#include "mouse-settings.hpp"
+#include "api/plugin-api.hpp"
#include <QDebug>
-#include "api/plugin-api.hpp"
+
using namespace options;
-class mouse : public IProtocol
+class mouse : public TR, public IProtocol
{
-public:
- mouse();
- module_status initialize() override { return status_ok(); }
- void pose( const double *headpose) override;
- QString game_name() override;
+ Q_OBJECT
- int last_x, last_y;
-private:
static int get_delta(int val, int prev);
static int get_value(double val, double sensitivity, bool is_rotation);
- struct mouse_settings s;
+ int last_x = 0, last_y = 0;
+ mouse_settings s;
+
+public:
+ mouse() = default;
+ module_status initialize() override { return status_ok(); }
+ void pose(const double* headpose, const double*) override;
+ QString game_name() override;
};
class MOUSEControls: public IProtocolDialog
{
Q_OBJECT
-public:
- MOUSEControls();
- void register_protocol(IProtocol *) {}
- void unregister_protocol() {}
-private:
- Ui::UICMOUSEControls ui;
+
+ Ui::UI_mouse ui;
mouse_settings s;
+
private slots:
void doOK();
void doCancel();
+
+public:
+ MOUSEControls();
+ void register_protocol(IProtocol*) override {}
+ void unregister_protocol() override {}
};
class mouseDll : public Metadata
{
-public:
- QString name() { return otr_tr("mouse emulation"); }
- QIcon icon() { return QIcon(":/images/mouse.png"); }
+ Q_OBJECT
+
+ QString name() override { return tr("mouse emulation"); }
+ QIcon icon() override { return QIcon(":/images/mouse.png"); }
};
diff --git a/proto-mouse/ftnoir_protocol_mouse_dialog.cpp b/proto-mouse/ftnoir_protocol_mouse_dialog.cpp
index 77b1ff2e..5646718c 100644
--- a/proto-mouse/ftnoir_protocol_mouse_dialog.cpp
+++ b/proto-mouse/ftnoir_protocol_mouse_dialog.cpp
@@ -3,19 +3,25 @@
MOUSEControls::MOUSEControls()
{
- ui.setupUi( this );
+ ui.setupUi(this);
- connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
- connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
+ connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &MOUSEControls::doOK);
+ connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &MOUSEControls::doCancel);
- tie_setting(s.Mouse_X, ui.cbxSelectMouse_X);
- tie_setting(s.Mouse_Y, ui.cbxSelectMouse_Y);
+ tie_setting(s.mouse_x, ui.axis_x);
+ tie_setting(s.mouse_y, ui.axis_y);
tie_setting(s.sensitivity_x, ui.sensitivity_x);
tie_setting(s.sensitivity_y, ui.sensitivity_y);
+
+ const int data[] = { input_direct, input_legacy };
+ for (unsigned k = 0; k < std::size(data); k++)
+ ui.input_method->setItemData(k, data[k]);
+ tie_setting(s.input_method, ui.input_method);
}
-void MOUSEControls::doOK() {
+void MOUSEControls::doOK()
+{
s.b->save();
close();
}
@@ -24,4 +30,3 @@ void MOUSEControls::doCancel()
{
close();
}
-
diff --git a/proto-mouse/lang/nl_NL.ts b/proto-mouse/lang/nl_NL.ts
index 68fa7065..13a75520 100644
--- a/proto-mouse/lang/nl_NL.ts
+++ b/proto-mouse/lang/nl_NL.ts
@@ -2,21 +2,13 @@
<!DOCTYPE TS>
<TS version="2.1" language="nl_NL">
<context>
- <name>UICMOUSEControls</name>
+ <name>UI_mouse</name>
<message>
- <source>Mouse protocol settings</source>
+ <source>Mouse protocol</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Map mouse X to:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Map mouse Y to:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Select Number</source>
+ <source>X axis</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -48,11 +40,37 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>X axis sensitivity</source>
+ <source>Y axis</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Y axis sensitivity</source>
+ <source>Sensitivity</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Method</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Direct input</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Legacy</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>mouse</name>
+ <message>
+ <source>Mouse tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>mouseDll</name>
+ <message>
+ <source>mouse emulation</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-mouse/lang/ru_RU.ts b/proto-mouse/lang/ru_RU.ts
index 39f27c12..bdadb6bd 100644
--- a/proto-mouse/lang/ru_RU.ts
+++ b/proto-mouse/lang/ru_RU.ts
@@ -2,21 +2,13 @@
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
<context>
- <name>UICMOUSEControls</name>
+ <name>UI_mouse</name>
<message>
- <source>Mouse protocol settings</source>
+ <source>Mouse protocol</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Map mouse X to:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Map mouse Y to:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Select Number</source>
+ <source>X axis</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -48,11 +40,37 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>X axis sensitivity</source>
+ <source>Y axis</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Y axis sensitivity</source>
+ <source>Sensitivity</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Method</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Direct input</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Legacy</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>mouse</name>
+ <message>
+ <source>Mouse tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>mouseDll</name>
+ <message>
+ <source>mouse emulation</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-mouse/lang/stub.ts b/proto-mouse/lang/stub.ts
index 3449ab53..2a811df1 100644
--- a/proto-mouse/lang/stub.ts
+++ b/proto-mouse/lang/stub.ts
@@ -2,21 +2,13 @@
<!DOCTYPE TS>
<TS version="2.1">
<context>
- <name>UICMOUSEControls</name>
+ <name>UI_mouse</name>
<message>
- <source>Mouse protocol settings</source>
+ <source>Mouse protocol</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Map mouse X to:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Map mouse Y to:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Select Number</source>
+ <source>X axis</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -48,11 +40,37 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>X axis sensitivity</source>
+ <source>Y axis</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Y axis sensitivity</source>
+ <source>Sensitivity</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Method</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Direct input</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Legacy</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>mouse</name>
+ <message>
+ <source>Mouse tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>mouseDll</name>
+ <message>
+ <source>mouse emulation</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-mouse/lang/zh_CN.ts b/proto-mouse/lang/zh_CN.ts
index 3449ab53..e69c4bfe 100644
--- a/proto-mouse/lang/zh_CN.ts
+++ b/proto-mouse/lang/zh_CN.ts
@@ -1,58 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
- <name>UICMOUSEControls</name>
+ <name>UI_mouse</name>
<message>
- <source>Mouse protocol settings</source>
+ <source>Mouse protocol</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Map mouse X to:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Map mouse Y to:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Select Number</source>
- <translation type="unfinished"></translation>
+ <source>X axis</source>
+ <translation>X è½´</translation>
</message>
<message>
<source>None</source>
- <translation type="unfinished"></translation>
+ <translation>æ— </translation>
</message>
<message>
<source>X</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>Y</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>Z</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>Yaw</source>
- <translation type="unfinished"></translation>
+ <translation>航å‘</translation>
</message>
<message>
<source>Pitch</source>
- <translation type="unfinished"></translation>
+ <translation>俯仰</translation>
</message>
<message>
<source>Roll</source>
+ <translation>滚转</translation>
+ </message>
+ <message>
+ <source>Y axis</source>
+ <translation>Y è½´</translation>
+ </message>
+ <message>
+ <source>Sensitivity</source>
+ <translation>çµæ•度</translation>
+ </message>
+ <message>
+ <source>Method</source>
+ <translation>æ–¹å¼</translation>
+ </message>
+ <message>
+ <source>Direct input</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>X axis sensitivity</source>
+ <source>Legacy</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>mouse</name>
+ <message>
+ <source>Mouse tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>mouseDll</name>
<message>
- <source>Y axis sensitivity</source>
+ <source>mouse emulation</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-mouse/mouse-settings.hpp b/proto-mouse/mouse-settings.hpp
index c29024ac..fda06166 100644
--- a/proto-mouse/mouse-settings.hpp
+++ b/proto-mouse/mouse-settings.hpp
@@ -2,22 +2,24 @@
#include "options/options.hpp"
+enum input_method {
+ input_direct = 0, input_legacy = 1,
+};
+
namespace mouse_impl {
using namespace options;
-struct mouse_settings : opts {
- value<int> Mouse_X, Mouse_Y;
- value<slider_value> sensitivity_x, sensitivity_y;
- mouse_settings() :
- opts("mouse-proto"),
- Mouse_X(b, "mouse-x", 0),
- Mouse_Y(b, "mouse-y", 0),
- sensitivity_x(b, "mouse-sensitivity-x", slider_value(200, 25, 500)),
- sensitivity_y(b, "mouse-sensitivity-y", slider_value(200, 25, 500))
- {}
+struct mouse_settings : opts
+{
+ value<int> mouse_x { b, "mouse-x", 0 }, mouse_y { b, "mouse-y", 0 };
+ value<slider_value> sensitivity_x { b, "mouse-sensitivity-x", { 200, 25, 500 } };
+ value<slider_value> sensitivity_y { b, "mouse-sensitivity-y", { 200, 25, 500 } };
+ value<input_method> input_method { b, "input-method", input_direct };
+
+ mouse_settings() : opts("mouse-proto") {}
};
} // ns mouse_impl
-using mouse_impl::mouse_settings;
+using mouse_settings = mouse_impl::mouse_settings;
diff --git a/proto-osc/CMakeLists.txt b/proto-osc/CMakeLists.txt
new file mode 100644
index 00000000..eeaf206c
--- /dev/null
+++ b/proto-osc/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(SDK_OSCPACK "" CACHE PATH "oscpack build directory")
+if(SDK_OSCPACK)
+ if(WIN32)
+ if(NOT EXISTS "${SDK_OSCPACK}/include/.")
+ message(FATAL_ERROR "SDK_OSCPACK should have 'include' subdirectory (or symlink) to src dir")
+ endif()
+ link_directories("${SDK_OSCPACK}")
+ include_directories("${SDK_OSCPACK}/include" "${SDK_OSCPACK}/include/oscpack")
+ else()
+ link_directories("${SDK_OSCPACK}/lib" "${SDK_OSCPACK}/lib32")
+ include_directories("${SDK_OSCPACK}/include/oscpack")
+ endif()
+ link_libraries(oscpack)
+ otr_module(proto-osc)
+endif()
diff --git a/proto-osc/dialog.cpp b/proto-osc/dialog.cpp
new file mode 100644
index 00000000..54bf6885
--- /dev/null
+++ b/proto-osc/dialog.cpp
@@ -0,0 +1,38 @@
+#include "dialog.hpp"
+#include <QHostAddress>
+#include <QPalette>
+
+void osc_dialog::host_address_edited(const QString& str)
+{
+ bool bad = QHostAddress{str}.isNull();
+ auto pal = pal_;
+ for (auto role : { QPalette::Highlight, QPalette::Window })
+ if (bad)
+ pal.setColor(role, Qt::red);
+ ui.address->setPalette(pal);
+}
+
+osc_dialog::osc_dialog() :
+ pal_{palette()}
+{
+ ui.setupUi( this );
+
+ tie_setting(s.address, ui.address);
+ tie_setting(s.port, ui.port);
+ connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &osc_dialog::doOK);
+ connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &osc_dialog::doCancel);
+ connect(ui.address, &QLineEdit::textChanged, this, &osc_dialog::host_address_edited);
+ host_address_edited(ui.address->text());
+}
+
+void osc_dialog::save() { s.b->save(); }
+void osc_dialog::reload() { s.b->reload(); }
+
+void osc_dialog::doOK() { s.b->save(); close(); }
+void osc_dialog::doCancel() { close(); }
+
+void osc_dialog::register_protocol(IProtocol*) {}
+void osc_dialog::unregister_protocol() {}
+
+bool osc_dialog::embeddable() noexcept { return true; }
+void osc_dialog::set_buttons_visible(bool x) noexcept { ui.buttonBox->setVisible(x); }
diff --git a/proto-osc/dialog.hpp b/proto-osc/dialog.hpp
new file mode 100644
index 00000000..29682843
--- /dev/null
+++ b/proto-osc/dialog.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "settings.hpp"
+#include "ui_dialog.h"
+#include "api/plugin-api.hpp"
+
+class osc_dialog: public IProtocolDialog
+{
+ Q_OBJECT
+
+public:
+ osc_dialog();
+ void register_protocol(IProtocol*) override;
+ void unregister_protocol() override;
+private:
+ void set_buttons_visible(bool x) noexcept override;
+ bool embeddable() noexcept override;
+ void save() override;
+ void reload() override;
+
+ Ui_OSC_Dialog ui;
+ osc_settings s;
+ const QPalette pal_;
+
+private slots:
+ void doOK();
+ void doCancel();
+ void host_address_edited(const QString& str);
+};
diff --git a/proto-osc/dialog.ui b/proto-osc/dialog.ui
new file mode 100644
index 00000000..5a078bbd
--- /dev/null
+++ b/proto-osc/dialog.ui
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>OSC_Dialog</class>
+ <widget class="QWidget" name="OSC_Dialog">
+ <property name="windowModality">
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>284</width>
+ <height>102</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>284</width>
+ <height>102</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>OSC protocol settings</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="osc-res.qrc">
+ <normaloff>:/images/osc-icon.png</normaloff>:/images/osc-icon.png</iconset>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QFormLayout" name="formLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Destination address</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="address">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="maxLength">
+ <number>256</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Port</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="port">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </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::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../gui/opentrack-res.qrc"/>
+ <include location="osc-res.qrc"/>
+ </resources>
+ <connections/>
+ <designerdata>
+ <property name="gridDeltaX">
+ <number>5</number>
+ </property>
+ <property name="gridDeltaY">
+ <number>5</number>
+ </property>
+ <property name="gridSnapX">
+ <bool>true</bool>
+ </property>
+ <property name="gridSnapY">
+ <bool>true</bool>
+ </property>
+ <property name="gridVisible">
+ <bool>false</bool>
+ </property>
+ </designerdata>
+ <slots>
+ <slot>startEngineClicked()</slot>
+ <slot>stopEngineClicked()</slot>
+ <slot>cameraSettingsClicked()</slot>
+ </slots>
+</ui>
diff --git a/proto-osc/images/osc-icon.png b/proto-osc/images/osc-icon.png
new file mode 100644
index 00000000..3ef546e9
--- /dev/null
+++ b/proto-osc/images/osc-icon.png
Binary files differ
diff --git a/tracker-rift-025/lang/nl_NL.ts b/proto-osc/lang/nl_NL.ts
index fef89934..260b7adc 100644
--- a/tracker-rift-025/lang/nl_NL.ts
+++ b/proto-osc/lang/nl_NL.ts
@@ -2,44 +2,43 @@
<!DOCTYPE TS>
<TS version="2.1" language="nl_NL">
<context>
- <name>dialog_rift_025</name>
+ <name>OSC_Dialog</name>
<message>
- <source>Oculus Rift tracker settings FaceTrackNoIR</source>
+ <source>OSC protocol settings</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Yaw spring</source>
+ <source>Port</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
+ <source>Destination address</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>osc_metadata</name>
<message>
- <source>Constant drift</source>
+ <source>Open Sound Control</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>osc_proto</name>
<message>
- <source>Deadzone</source>
+ <source>Open Sound Control</source>
<translation type="unfinished"></translation>
</message>
-</context>
-<context>
- <name>rift_tracker_025</name>
<message>
- <source>Unable to create Rift sensor</source>
+ <source>Error binding socket to INADDR_ANY</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Unable to enumerate Rift tracker</source>
+ <source>%1: %2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Unable to start Rift tracker</source>
+ <source>Invalid destination address &apos;%1&apos;</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rift-025/lang/ru_RU.ts b/proto-osc/lang/ru_RU.ts
index 8ab48509..498d68d6 100644
--- a/tracker-rift-025/lang/ru_RU.ts
+++ b/proto-osc/lang/ru_RU.ts
@@ -2,44 +2,43 @@
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
<context>
- <name>dialog_rift_025</name>
+ <name>OSC_Dialog</name>
<message>
- <source>Oculus Rift tracker settings FaceTrackNoIR</source>
+ <source>OSC protocol settings</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Yaw spring</source>
+ <source>Port</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
+ <source>Destination address</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>osc_metadata</name>
<message>
- <source>Constant drift</source>
+ <source>Open Sound Control</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>osc_proto</name>
<message>
- <source>Deadzone</source>
+ <source>Open Sound Control</source>
<translation type="unfinished"></translation>
</message>
-</context>
-<context>
- <name>rift_tracker_025</name>
<message>
- <source>Unable to create Rift sensor</source>
+ <source>Error binding socket to INADDR_ANY</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Unable to enumerate Rift tracker</source>
+ <source>%1: %2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Unable to start Rift tracker</source>
+ <source>Invalid destination address &apos;%1&apos;</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rift-025/lang/stub.ts b/proto-osc/lang/stub.ts
index cf2a32a9..20828cbd 100644
--- a/tracker-rift-025/lang/stub.ts
+++ b/proto-osc/lang/stub.ts
@@ -2,44 +2,43 @@
<!DOCTYPE TS>
<TS version="2.1">
<context>
- <name>dialog_rift_025</name>
+ <name>OSC_Dialog</name>
<message>
- <source>Oculus Rift tracker settings FaceTrackNoIR</source>
+ <source>OSC protocol settings</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Yaw spring</source>
+ <source>Port</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
+ <source>Destination address</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>osc_metadata</name>
<message>
- <source>Constant drift</source>
+ <source>Open Sound Control</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>osc_proto</name>
<message>
- <source>Deadzone</source>
+ <source>Open Sound Control</source>
<translation type="unfinished"></translation>
</message>
-</context>
-<context>
- <name>rift_tracker_025</name>
<message>
- <source>Unable to create Rift sensor</source>
+ <source>Error binding socket to INADDR_ANY</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Unable to enumerate Rift tracker</source>
+ <source>%1: %2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Unable to start Rift tracker</source>
+ <source>Invalid destination address &apos;%1&apos;</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rift-025/lang/zh_CN.ts b/proto-osc/lang/zh_CN.ts
index cf2a32a9..e0d49844 100644
--- a/tracker-rift-025/lang/zh_CN.ts
+++ b/proto-osc/lang/zh_CN.ts
@@ -1,45 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
- <name>dialog_rift_025</name>
+ <name>OSC_Dialog</name>
<message>
- <source>Oculus Rift tracker settings FaceTrackNoIR</source>
+ <source>OSC protocol settings</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Yaw spring</source>
+ <source>Port</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
+ <source>Destination address</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>osc_metadata</name>
<message>
- <source>Constant drift</source>
+ <source>Open Sound Control</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>osc_proto</name>
<message>
- <source>Deadzone</source>
+ <source>Open Sound Control</source>
<translation type="unfinished"></translation>
</message>
-</context>
-<context>
- <name>rift_tracker_025</name>
<message>
- <source>Unable to create Rift sensor</source>
+ <source>Error binding socket to INADDR_ANY</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Unable to enumerate Rift tracker</source>
+ <source>%1: %2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Unable to start Rift tracker</source>
+ <source>Invalid destination address &apos;%1&apos;</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-osc/metadata.cpp b/proto-osc/metadata.cpp
new file mode 100644
index 00000000..9d69347d
--- /dev/null
+++ b/proto-osc/metadata.cpp
@@ -0,0 +1,8 @@
+#include "metadata.hpp"
+#include "proto.hpp"
+#include "dialog.hpp"
+
+QString osc_metadata::name() { return tr("Open Sound Control"); }
+QIcon osc_metadata::icon() { return QIcon(":/images/osc-icon.png"); }
+
+OPENTRACK_DECLARE_PROTOCOL(osc_proto, osc_dialog, osc_metadata)
diff --git a/proto-osc/metadata.hpp b/proto-osc/metadata.hpp
new file mode 100644
index 00000000..c0a947aa
--- /dev/null
+++ b/proto-osc/metadata.hpp
@@ -0,0 +1,10 @@
+#pragma once
+#include "api/plugin-api.hpp"
+
+class osc_metadata : public Metadata
+{
+ Q_OBJECT
+
+ QString name() override;
+ QIcon icon() override;
+};
diff --git a/proto-osc/osc-res.qrc b/proto-osc/osc-res.qrc
new file mode 100644
index 00000000..ade4c629
--- /dev/null
+++ b/proto-osc/osc-res.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>images/osc-icon.png</file>
+ </qresource>
+</RCC>
diff --git a/proto-osc/proto.cpp b/proto-osc/proto.cpp
new file mode 100644
index 00000000..2f90957c
--- /dev/null
+++ b/proto-osc/proto.cpp
@@ -0,0 +1,54 @@
+#include "proto.hpp"
+#include "ui_dialog.h"
+#include "api/plugin-api.hpp"
+#include <QQuaternion>
+#include <QHostAddress>
+#include "osc/OscOutboundPacketStream.h"
+
+osc_proto::osc_proto()
+{
+ auto reload_fn = [this] {
+ dest = QHostAddress{s.address };
+ port = (unsigned short)s.port;
+ };
+ connect(&*s.b, &bundle_::changed, this, reload_fn);
+ connect(&*s.b, &bundle_::reloading, this, reload_fn);
+}
+
+void osc_proto::pose(const double* data, const double*)
+{
+ if (dest.isNull())
+ return;
+
+ static constexpr unsigned buffer_size = 1024;
+ char buffer[buffer_size] = {};
+ osc::OutboundPacketStream p{buffer, buffer_size};
+ auto q = QQuaternion::fromEulerAngles((float)data[Pitch], (float)data[Yaw], (float)-data[Roll]).normalized();
+ p << osc::BeginMessage("/bridge/quat") << q.scalar() << q.x() << q.y() << q.z() << osc::EndMessage;
+ sock.writeDatagram(p.Data(), (int)p.Size(), dest, port);
+}
+
+module_status osc_proto::initialize()
+{
+ QString error;
+
+ dest = QHostAddress{s.address };
+ port = (unsigned short)s.port;
+
+ if (dest.isNull())
+ {
+ error = tr("Invalid destination address '%1'").arg(s.address);
+ goto fail;
+ }
+
+ if (!sock.bind())
+ {
+ error = tr("Error binding socket to INADDR_ANY");
+ goto fail;
+ }
+
+ return status_ok();
+
+fail:
+ return { tr("%1: %2").arg(error, sock.errorString()) };
+}
diff --git a/proto-osc/proto.hpp b/proto-osc/proto.hpp
new file mode 100644
index 00000000..53a5dee4
--- /dev/null
+++ b/proto-osc/proto.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+/* Copyright (c) 2023 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * 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 "settings.hpp"
+#include "api/plugin-api.hpp"
+#include <QUdpSocket>
+#include <QHostAddress>
+
+class osc_proto : public QObject, public IProtocol
+{
+ Q_OBJECT
+
+ osc_settings s;
+ QUdpSocket sock;
+ QHostAddress dest;
+ unsigned short port = 0;
+
+public:
+ osc_proto();
+ module_status initialize() override;
+ void pose(const double *headpose, const double*) override;
+ QString game_name() override { return tr("Open Sound Control"); }
+};
diff --git a/proto-osc/settings.hpp b/proto-osc/settings.hpp
new file mode 100644
index 00000000..edeb45c6
--- /dev/null
+++ b/proto-osc/settings.hpp
@@ -0,0 +1,14 @@
+#pragma once
+#include <QString>
+#include "options/options.hpp"
+
+using namespace options;
+
+struct osc_settings : opts
+{
+ value<QString> address;
+ value<int> port;
+ osc_settings() : opts("proto-osc"), address{b, "address", "127.0.0.1"},
+ port(b, "port", 53101)
+ {}
+};
diff --git a/proto-simconnect/CMakeLists.txt b/proto-simconnect/CMakeLists.txt
index 26047b6f..f5cec5ac 100644
--- a/proto-simconnect/CMakeLists.txt
+++ b/proto-simconnect/CMakeLists.txt
@@ -1,3 +1,3 @@
-if(WIN32)
+if(WIN32 AND opentrack-intel)
otr_module(proto-simconnect)
endif()
diff --git a/proto-simconnect/ftnoir-protocol-sc.rc b/proto-simconnect/ftnoir-protocol-sc.rc
index f1a7b531..0d501812 100644
--- a/proto-simconnect/ftnoir-protocol-sc.rc
+++ b/proto-simconnect/ftnoir-protocol-sc.rc
@@ -1,7 +1,7 @@
#define RT_MANIFEST 24
-142 RT_MANIFEST fsx_rtm.manifest
-143 RT_MANIFEST fsx_sp1.manifest
-144 RT_MANIFEST fsx_sp2.manifest
-145 RT_MANIFEST fsx_p3d_sp2_xpack.manifest
+142 RT_MANIFEST fsx_p3d_sp2_xpack.manifest
+143 RT_MANIFEST fsx_rtm.manifest
+144 RT_MANIFEST fsx_sp1.manifest
+145 RT_MANIFEST fsx_sp2.manifest
146 RT_MANIFEST fsx_steam_orig.manifest
147 RT_MANIFEST fsx_steam_last.manifest
diff --git a/proto-simconnect/ftnoir_protocol_sc.cpp b/proto-simconnect/ftnoir_protocol_sc.cpp
index f2a9dd14..ca76e0ce 100644
--- a/proto-simconnect/ftnoir_protocol_sc.cpp
+++ b/proto-simconnect/ftnoir_protocol_sc.cpp
@@ -2,8 +2,8 @@
* *
* ISC License (ISC) *
* *
- * Copyright (c) 2015, Wim Vriend
- * Copyright (c) 2014, Stanislaw Halik <sthalik@misaki.pl>
+ * Copyright (c) 2015, Wim Vriend *
+ * Copyright (c) 2014, 2017, 2019 Stanislaw Halik *
* *
* Permission to use, copy, modify, and/or distribute this software for any *
* purpose with or without fee is hereby granted, provided that the above *
@@ -13,10 +13,7 @@
#include "api/plugin-api.hpp"
#include "compat/timer.hpp"
#include "compat/library-path.hpp"
-
-simconnect::simconnect() : hSimConnect(nullptr), should_reconnect(false)
-{
-}
+#include "compat/activation-context.hpp"
simconnect::~simconnect()
{
@@ -26,65 +23,62 @@ simconnect::~simconnect()
void simconnect::run()
{
- HANDLE event = CreateEventA(NULL, FALSE, FALSE, nullptr);
+ HANDLE event = CreateEventA(nullptr, FALSE, FALSE, nullptr);
if (event == nullptr)
{
- qDebug() << "simconnect: event create" << GetLastError();
+ qDebug() << "fsx: create event failed, error code" << GetLastError();
return;
}
+ constexpr unsigned sleep_time = 5;
+
while (!isInterruptionRequested())
{
HRESULT hr;
+ reconnect = false;
+ handle = nullptr;
- if (SUCCEEDED(hr = simconnect_open(&hSimConnect, "opentrack", nullptr, 0, event, 0)))
+ if (!SUCCEEDED(hr = simconnect_open(&handle, "opentrack", nullptr, 0, event, 0)))
+ qDebug() << "fsx: connect failed, retry in" << sleep_time << "seconds...";
+ else
{
- if (!SUCCEEDED(hr = simconnect_subscribetosystemevent(hSimConnect, 0, "Frame")))
+ if (!SUCCEEDED(hr = simconnect_subscribe(handle, 0, "1sec")))
+ qDebug() << "fsx: can't subscribe to frame event:" << (void*)hr;
+ else
{
- qDebug() << "simconnect: can't subscribe to frame event:" << hr;
- }
-
- Timer tm;
- should_reconnect = false;
-
- if (SUCCEEDED(hr))
while (!isInterruptionRequested())
{
- if (should_reconnect)
- break;
+ constexpr int max_idle_ms = 2000;
- if (WaitForSingleObject(event, 100) == WAIT_OBJECT_0)
+ if (WaitForSingleObject(event, max_idle_ms) != WAIT_OBJECT_0)
{
- tm.start();
-
- if (!SUCCEEDED(hr = simconnect_calldispatch(hSimConnect, processNextSimconnectEvent, reinterpret_cast<void*>(this))))
- {
- qDebug() << "simconnect: calldispatch failed:" << hr;
- break;
- }
+ qDebug() << "fsx: timeout reached, reconnecting";
+ break;
}
- else
- {
- const int idle_seconds = tm.elapsed_seconds();
- constexpr int max_idle_seconds = 2;
+ if (reconnect.load(std::memory_order_relaxed))
+ break;
- if (idle_seconds >= max_idle_seconds)
- {
- qDebug() << "simconnect: reconnect";
- break;
- }
+ if (!SUCCEEDED(hr = simconnect_calldispatch(handle, event_handler, (void*)this)))
+ {
+ qDebug() << "fsx: calldispatch failed:" << (void*)hr;
+ break;
}
- }
+ }
+ }
- (void) simconnect_close(hSimConnect);
+ QMutexLocker l(&mtx);
+ (void)simconnect_close(handle);
+ handle = nullptr;
}
- else
- qDebug() << "simconnect: can't open handle:" << hr;
- if (!isInterruptionRequested())
- Sleep(3000);
+ for (unsigned k = 0; k < sleep_time * 25; k++)
+ {
+ if (isInterruptionRequested())
+ break;
+ Sleep(1000 / 25);
+ }
}
qDebug() << "simconnect: exit";
@@ -92,126 +86,70 @@ void simconnect::run()
CloseHandle(event);
}
-void simconnect::pose( const double *headpose )
+void simconnect::pose(const double* pose, const double*)
{
- // euler degrees
- virtSCRotX = float(-headpose[Pitch]);
- virtSCRotY = float(headpose[Yaw]);
- virtSCRotZ = float(headpose[Roll]);
-
- // cm to meters
- virtSCPosX = float(-headpose[TX]/100);
- virtSCPosY = float(headpose[TY]/100);
- virtSCPosZ = float(-headpose[TZ]/100);
+ data[Pitch] = (float)-pose[Pitch];
+ data[Yaw] = (float)pose[Yaw];
+ data[Roll] = (float)pose[Roll];
+
+ constexpr float to_meters = 1e-2f;
+ data[TX] = (float)-pose[TX] * to_meters;
+ data[TY] = (float)pose[TY] * to_meters;
+ data[TZ] = (float)-pose[TZ] * to_meters;
+
+ QMutexLocker l(&mtx);
+ if (handle)
+ (void)simconnect_set6DOF(handle,
+ data[TX], data[TY], data[TZ],
+ data[Pitch], data[Roll], data[Yaw]);
}
-#ifdef __GNUC__
-# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
-#endif
-
-class ActivationContext
-{
-public:
- ActivationContext(const int resid) :
- ok(false)
- {
- hactctx = INVALID_HANDLE_VALUE;
- actctx_cookie = 0;
- ACTCTXA actx = {};
- actx.cbSize = sizeof(actx);
- actx.lpResourceName = MAKEINTRESOURCEA(resid);
- actx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID;
-#ifdef _MSC_VER
-# define PREFIX ""
-#else
-# define PREFIX "lib"
-#endif
- static const QString prefix = OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH;
- QString path = prefix + PREFIX "opentrack-proto-simconnect.dll";
- QByteArray name = QFile::encodeName(path);
- actx.lpSource = name.constData();
- hactctx = CreateActCtxA(&actx);
- actctx_cookie = 0;
- if (hactctx != INVALID_HANDLE_VALUE)
- {
- if (!ActivateActCtx(hactctx, &actctx_cookie))
- {
- qDebug() << "simconnect: can't set win32 activation context" << GetLastError();
- ReleaseActCtx(hactctx);
- hactctx = INVALID_HANDLE_VALUE;
- }
- else
- ok = true;
- } else {
- qDebug() << "simconnect: can't create win32 activation context" << GetLastError();
- }
- }
- ~ActivationContext() {
- if (hactctx != INVALID_HANDLE_VALUE)
- {
- DeactivateActCtx(0, actctx_cookie);
- ReleaseActCtx(hactctx);
- }
- }
- bool is_ok() const { return ok; }
-
-private:
- ULONG_PTR actctx_cookie;
- HANDLE hactctx;
- bool ok;
-};
-
module_status simconnect::initialize()
{
- if (!SCClientLib.isLoaded())
+ if (!library.isLoaded())
{
- ActivationContext ctx(142 + static_cast<int>(s.sxs_manifest));
+ constexpr int resource_offset = 142;
+ activation_context ctx("opentrack-proto-simconnect" "." OPENTRACK_LIBRARY_EXTENSION,
+ resource_offset + s.sxs_manifest);
- if (ctx.is_ok())
+ if (ctx)
{
- SCClientLib.setFileName("SimConnect.dll");
- SCClientLib.setLoadHints(QLibrary::PreventUnloadHint);
- if (!SCClientLib.load())
- return error(tr("dll load failed -- %1").arg(SCClientLib.errorString()));
+ library.setFileName("SimConnect.dll");
+ library.setLoadHints(QLibrary::PreventUnloadHint | QLibrary::ResolveAllSymbolsHint);
+ if (!library.load())
+ return error(tr("dll load failed: %1").arg(library.errorString()));
}
else
- return error(_("can't load SDK -- check selected simconnect version"));
+ // XXX TODO add instructions for fsx and p3d -sh 20190128
+ return error(tr("Install FSX/Prepar3D SimConnect SDK."));
}
- simconnect_open = (importSimConnect_Open) SCClientLib.resolve("SimConnect_Open");
- if (simconnect_open == NULL) {
- return error("Open function not found in DLL!");
- }
- simconnect_set6DOF = (importSimConnect_CameraSetRelative6DOF) SCClientLib.resolve("SimConnect_CameraSetRelative6DOF");
- if (simconnect_set6DOF == NULL) {
- return error("CameraSetRelative6DOF function not found in DLL!");
- }
- simconnect_close = (importSimConnect_Close) SCClientLib.resolve("SimConnect_Close");
- if (simconnect_close == NULL) {
- return error("Close function not found in DLL!");
- }
+ using ptr = decltype(library.resolve(""));
- simconnect_calldispatch = (importSimConnect_CallDispatch) SCClientLib.resolve("SimConnect_CallDispatch");
- if (simconnect_calldispatch == NULL) {
- return error("CallDispatch function not found in DLL!");
- }
+ struct {
+ const char* name;
+ ptr& place;
+ } list[] = {
+ { "SimConnect_Open", (ptr&)simconnect_open },
+ { "SimConnect_CameraSetRelative6DOF", (ptr&)simconnect_set6DOF },
+ { "SimConnect_Close", (ptr&)simconnect_close },
+ { "SimConnect_CallDispatch", (ptr&)simconnect_calldispatch },
+ { "SimConnect_SubscribeToSystemEvent", (ptr&)simconnect_subscribe },
+ };
- simconnect_subscribetosystemevent = (importSimConnect_SubscribeToSystemEvent) SCClientLib.resolve("SimConnect_SubscribeToSystemEvent");
- if (simconnect_subscribetosystemevent == NULL) {
- return error("SubscribeToSystemEvent function not found in DLL!");
+ for (auto& x : list)
+ {
+ x.place = library.resolve(x.name);
+ if (!x.place)
+ return error(tr("can't import %1: %2").arg(x.name, library.errorString()));
}
start();
- return status_ok();
+ return {};
}
-void simconnect::handle()
-{
- (void) simconnect_set6DOF(hSimConnect, virtSCPosX, virtSCPosY, virtSCPosZ, virtSCRotX, virtSCRotZ, virtSCRotY);
-}
-
-void CALLBACK simconnect::processNextSimconnectEvent(SIMCONNECT_RECV* pData, DWORD, void *self_)
+void simconnect::event_handler(SIMCONNECT_RECV* pData, DWORD, void* self_)
{
simconnect& self = *reinterpret_cast<simconnect*>(self_);
@@ -220,17 +158,19 @@ void CALLBACK simconnect::processNextSimconnectEvent(SIMCONNECT_RECV* pData, DWO
default:
break;
case SIMCONNECT_RECV_ID_EXCEPTION:
- qDebug() << "simconnect: got exception";
- //self.should_reconnect = true;
+ // CAVEAT: can't reconnect here, it breaks Prepar3D.
+ // the timer on the event handle will take care of failures.
break;
case SIMCONNECT_RECV_ID_QUIT:
- qDebug() << "simconnect: got quit event";
- self.should_reconnect = true;
- break;
- case SIMCONNECT_RECV_ID_EVENT_FRAME:
- self.handle();
+ qDebug() << "fsx: got quit event";
+ self.reconnect = true;
break;
}
}
-OPENTRACK_DECLARE_PROTOCOL(simconnect, SCControls, simconnectDll)
+QString simconnect::game_name()
+{
+ return tr("FSX / Prepar3D");
+}
+
+OPENTRACK_DECLARE_PROTOCOL(simconnect, simconnect_ui, simconnect_metadata)
diff --git a/proto-simconnect/ftnoir_protocol_sc.h b/proto-simconnect/ftnoir_protocol_sc.h
index 6f9a908f..df3a538d 100644
--- a/proto-simconnect/ftnoir_protocol_sc.h
+++ b/proto-simconnect/ftnoir_protocol_sc.h
@@ -3,7 +3,7 @@
* ISC License (ISC) *
* *
* Copyright (c) 2015, Wim Vriend *
- * Copyright (c) 2014, 2017 Stanislaw Halik *
+ * Copyright (c) 2014, 2017, 2019 Stanislaw Halik *
* *
* Permission to use, copy, modify, and/or distribute this software for any *
* purpose with or without fee is hereby granted, provided that the above *
@@ -16,6 +16,7 @@
#include <atomic>
#include <QThread>
+#include <QMutex>
#include <QMessageBox>
#include <QSettings>
#include <QLibrary>
@@ -30,7 +31,7 @@ struct settings : opts {
value<int> sxs_manifest;
settings() :
opts("proto-simconnect"),
- sxs_manifest(b, "simconnect-manifest", 2)
+ sxs_manifest(b, "simconnect-manifest", 0)
{}
};
@@ -38,79 +39,72 @@ class simconnect : private QThread, public IProtocol
{
Q_OBJECT
public:
- simconnect();
+ simconnect() = default;
~simconnect() override;
module_status initialize() override;
- void pose(const double* headpose);
- void handle();
- QString game_name() {
- return otr_tr("FS2004/FSX");
- }
+ void pose(const double* headpose, const double*) override;
+ QString game_name() override;
+ void run() override;
+
private:
enum {
- SIMCONNECT_RECV_ID_NULL,
SIMCONNECT_RECV_ID_EXCEPTION = 2,
SIMCONNECT_RECV_ID_QUIT = 3,
SIMCONNECT_RECV_ID_EVENT_FRAME = 7,
};
- #pragma pack(push, 1)
struct SIMCONNECT_RECV
{
DWORD dwSize;
DWORD dwVersion;
DWORD dwID;
};
- #pragma pack(pop)
- typedef void (CALLBACK *DispatchProc)(SIMCONNECT_RECV*, DWORD, void*);
+ typedef void (CALLBACK *SC_DispatchProc)(SIMCONNECT_RECV*, DWORD, void*);
- typedef HRESULT (WINAPI *importSimConnect_Open)(HANDLE * phSimConnect, LPCSTR szName, HWND hWnd, DWORD UserEventWin32, HANDLE hEventHandle, DWORD ConfigIndex);
- typedef HRESULT (WINAPI *importSimConnect_Close)(HANDLE hSimConnect);
- typedef HRESULT (WINAPI *importSimConnect_CameraSetRelative6DOF)(HANDLE hSimConnect, float fDeltaX, float fDeltaY, float fDeltaZ, float fPitchDeg, float fBankDeg, float fHeadingDeg);
- typedef HRESULT (WINAPI *importSimConnect_CallDispatch)(HANDLE hSimConnect, DispatchProc pfcnDispatch, void * pContext);
- typedef HRESULT (WINAPI *importSimConnect_SubscribeToSystemEvent)(HANDLE hSimConnect, DWORD EventID, const char * SystemEventName);
+ typedef HRESULT (WINAPI *SC_Open)(HANDLE* phSimConnect, LPCSTR szName, HWND hWnd, DWORD UserEventWin32, HANDLE hEventHandle, DWORD ConfigIndex);
+ typedef HRESULT (WINAPI *SC_Close)(HANDLE hSimConnect);
+ typedef HRESULT (WINAPI *SC_CameraSetRelative6DOF)(HANDLE hSimConnect, float fDeltaX, float fDeltaY, float fDeltaZ, float fPitchDeg, float fBankDeg, float fHeadingDeg);
+ typedef HRESULT (WINAPI *SC_CallDispatch)(HANDLE hSimConnect, SC_DispatchProc pfcnDispatch, void * pContext);
+ typedef HRESULT (WINAPI *SC_SubscribeToSystemEvent)(HANDLE hSimConnect, DWORD EventID, const char * SystemEventName);
- void run() override;
+ float data[6] {};
+ QMutex mtx;
+
+ SC_Open simconnect_open = nullptr;
+ SC_Close simconnect_close = nullptr;
+ SC_CameraSetRelative6DOF simconnect_set6DOF = nullptr;
+ SC_CallDispatch simconnect_calldispatch = nullptr;
+ SC_SubscribeToSystemEvent simconnect_subscribe = nullptr;
- std::atomic<float> virtSCPosX;
- std::atomic<float> virtSCPosY;
- std::atomic<float> virtSCPosZ;
- std::atomic<float> virtSCRotX;
- std::atomic<float> virtSCRotY;
- std::atomic<float> virtSCRotZ;
-
- importSimConnect_Open simconnect_open;
- importSimConnect_Close simconnect_close;
- importSimConnect_CameraSetRelative6DOF simconnect_set6DOF;
- importSimConnect_CallDispatch simconnect_calldispatch;
- importSimConnect_SubscribeToSystemEvent simconnect_subscribetosystemevent;
-
- HANDLE hSimConnect;
- std::atomic<bool> should_reconnect;
- static void CALLBACK processNextSimconnectEvent(SIMCONNECT_RECV* pData, DWORD cbData, void *pContext);
+ HANDLE handle = nullptr;
+ std::atomic<bool> reconnect = false;
+ static void CALLBACK event_handler(SIMCONNECT_RECV* pData, DWORD cbData, void* pContext);
settings s;
- QLibrary SCClientLib;
+ QLibrary library;
};
-class SCControls: public IProtocolDialog
+class simconnect_ui: public IProtocolDialog
{
Q_OBJECT
-public:
- SCControls();
- void register_protocol(IProtocol *) {}
- void unregister_protocol() {}
-private:
+
Ui::UICSCControls ui;
settings s;
+
+public:
+ simconnect_ui();
+ void register_protocol(IProtocol *) override {}
+ void unregister_protocol() override {}
+
private slots:
void doOK();
void doCancel();
};
-class simconnectDll : public Metadata
+class simconnect_metadata : public Metadata
{
-public:
- QString name() { return otr_tr("Microsoft FSX SimConnect"); }
- QIcon icon() { return QIcon(":/images/fsx.png"); }
+ Q_OBJECT
+
+ QString name() override { return tr("Microsoft FSX SimConnect"); }
+ QIcon icon() override { return QIcon(":/images/fsx.png"); }
};
diff --git a/proto-simconnect/ftnoir_protocol_sc_dialog.cpp b/proto-simconnect/ftnoir_protocol_sc_dialog.cpp
index a14c6b44..c329e75d 100644
--- a/proto-simconnect/ftnoir_protocol_sc_dialog.cpp
+++ b/proto-simconnect/ftnoir_protocol_sc_dialog.cpp
@@ -12,23 +12,23 @@
#include <QDebug>
#include "api/plugin-api.hpp"
-SCControls::SCControls()
+simconnect_ui::simconnect_ui()
{
- ui.setupUi( this );
+ 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()));
tie_setting(s.sxs_manifest, ui.comboBox);
}
-void SCControls::doOK() {
+void simconnect_ui::doOK()
+{
s.b->save();
close();
}
-void SCControls::doCancel()
+void simconnect_ui::doCancel()
{
close();
}
diff --git a/proto-simconnect/ftnoir_sccontrols.ui b/proto-simconnect/ftnoir_sccontrols.ui
index 6439550f..c272d5da 100644
--- a/proto-simconnect/ftnoir_sccontrols.ui
+++ b/proto-simconnect/ftnoir_sccontrols.ui
@@ -53,22 +53,22 @@
</property>
<item>
<property name="text">
- <string>RTM</string>
+ <string>Prepar3d / SP2 XPACK</string>
</property>
</item>
<item>
<property name="text">
- <string>SP1</string>
+ <string>RTM</string>
</property>
</item>
<item>
<property name="text">
- <string>SP2 -- Acceleration</string>
+ <string>SP1</string>
</property>
</item>
<item>
<property name="text">
- <string>Prepar3d SP2 XPACK</string>
+ <string>SP2 -- Acceleration</string>
</property>
</item>
<item>
@@ -78,7 +78,7 @@
</item>
<item>
<property name="text">
- <string>Steam FSX (new)</string>
+ <string>Steam FSX (newer)</string>
</property>
</item>
</widget>
diff --git a/proto-simconnect/lang/nl_NL.ts b/proto-simconnect/lang/nl_NL.ts
index 9c0b83db..5b9acbb3 100644
--- a/proto-simconnect/lang/nl_NL.ts
+++ b/proto-simconnect/lang/nl_NL.ts
@@ -24,26 +24,45 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Prepar3d SP2 XPACK</source>
+ <source>Steam FSX (older)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Steam FSX (older)</source>
+ <source>You need to install SimConnect SDK for your FSX version. For the Steam version, choose SP1 in the above combo box.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Steam FSX (new)</source>
+ <source>Prepar3d / SP2 XPACK</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>You need to install SimConnect SDK for your FSX version. For the Steam version, choose SP1 in the above combo box.</source>
+ <source>Steam FSX (newer)</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>simconnect</name>
<message>
- <source>dll load failed -- %1</source>
+ <source>dll load failed: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>can&apos;t import %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Install FSX/Prepar3D SimConnect SDK.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FSX / Prepar3D</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>simconnect_metadata</name>
+ <message>
+ <source>Microsoft FSX SimConnect</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-simconnect/lang/ru_RU.ts b/proto-simconnect/lang/ru_RU.ts
index d4bd343b..0d5bb1c3 100644
--- a/proto-simconnect/lang/ru_RU.ts
+++ b/proto-simconnect/lang/ru_RU.ts
@@ -24,26 +24,45 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Prepar3d SP2 XPACK</source>
+ <source>Steam FSX (older)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Steam FSX (older)</source>
+ <source>You need to install SimConnect SDK for your FSX version. For the Steam version, choose SP1 in the above combo box.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Steam FSX (new)</source>
+ <source>Prepar3d / SP2 XPACK</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>You need to install SimConnect SDK for your FSX version. For the Steam version, choose SP1 in the above combo box.</source>
+ <source>Steam FSX (newer)</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>simconnect</name>
<message>
- <source>dll load failed -- %1</source>
+ <source>dll load failed: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>can&apos;t import %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Install FSX/Prepar3D SimConnect SDK.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FSX / Prepar3D</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>simconnect_metadata</name>
+ <message>
+ <source>Microsoft FSX SimConnect</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-simconnect/lang/stub.ts b/proto-simconnect/lang/stub.ts
index 73608a68..50d0fa8a 100644
--- a/proto-simconnect/lang/stub.ts
+++ b/proto-simconnect/lang/stub.ts
@@ -24,26 +24,45 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Prepar3d SP2 XPACK</source>
+ <source>Steam FSX (older)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Steam FSX (older)</source>
+ <source>You need to install SimConnect SDK for your FSX version. For the Steam version, choose SP1 in the above combo box.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Steam FSX (new)</source>
+ <source>Prepar3d / SP2 XPACK</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>You need to install SimConnect SDK for your FSX version. For the Steam version, choose SP1 in the above combo box.</source>
+ <source>Steam FSX (newer)</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>simconnect</name>
<message>
- <source>dll load failed -- %1</source>
+ <source>dll load failed: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>can&apos;t import %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Install FSX/Prepar3D SimConnect SDK.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FSX / Prepar3D</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>simconnect_metadata</name>
+ <message>
+ <source>Microsoft FSX SimConnect</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-simconnect/lang/zh_CN.ts b/proto-simconnect/lang/zh_CN.ts
index 73608a68..86e487f2 100644
--- a/proto-simconnect/lang/zh_CN.ts
+++ b/proto-simconnect/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>UICSCControls</name>
<message>
@@ -24,26 +24,45 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Prepar3d SP2 XPACK</source>
+ <source>Steam FSX (older)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Steam FSX (older)</source>
+ <source>You need to install SimConnect SDK for your FSX version. For the Steam version, choose SP1 in the above combo box.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Steam FSX (new)</source>
+ <source>Prepar3d / SP2 XPACK</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>You need to install SimConnect SDK for your FSX version. For the Steam version, choose SP1 in the above combo box.</source>
+ <source>Steam FSX (newer)</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>simconnect</name>
<message>
- <source>dll load failed -- %1</source>
+ <source>dll load failed: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>can&apos;t import %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Install FSX/Prepar3D SimConnect SDK.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FSX / Prepar3D</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>simconnect_metadata</name>
+ <message>
+ <source>Microsoft FSX SimConnect</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-simconnect/manifest-template.in b/proto-simconnect/manifest-template.in
index fdf2c74a..69966102 100644
--- a/proto-simconnect/manifest-template.in
+++ b/proto-simconnect/manifest-template.in
@@ -1,8 +1,8 @@
-<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
-<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
- <dependency>
- <dependentAssembly>
- <assemblyIdentity type='win32' name='Microsoft.FlightSimulator.SimConnect' version='VERSION' processorArchitecture='x86' publicKeyToken='67c7c14424d61b5b' />
- </dependentAssembly>
- </dependency>
-</assembly>
+<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
+<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity type='win32' name='Microsoft.FlightSimulator.SimConnect' version='VERSION' processorArchitecture='x86' publicKeyToken='67c7c14424d61b5b' />
+ </dependentAssembly>
+ </dependency>
+</assembly>
diff --git a/proto-udp/ftnoir_protocol_ftn.cpp b/proto-udp/ftnoir_protocol_ftn.cpp
index 4e846114..c67c39b8 100644
--- a/proto-udp/ftnoir_protocol_ftn.cpp
+++ b/proto-udp/ftnoir_protocol_ftn.cpp
@@ -16,18 +16,23 @@ udp::udp()
{
set_dest_address();
- QObject::connect(s.b.get(), &bundle_::changed,
+ QObject::connect(&*s.b, &bundle_::changed,
this, &udp::set_dest_address,
- Qt::QueuedConnection);
+ Qt::DirectConnection);
}
-void udp::pose(const double *headpose) {
+void udp::pose(const double *headpose, const double*)
+{
+ QMutexLocker l(&lock);
+
outSocket.writeDatagram((const char *) headpose, sizeof(double[6]), dest_ip, dest_port);
}
void udp::set_dest_address()
{
- dest_port = s.port;
+ QMutexLocker l(&lock);
+
+ dest_port = (unsigned short)s.port;
dest_ip = QHostAddress((s.ip1.to<unsigned>() & 0xff) << 24 |
(s.ip2.to<unsigned>() & 0xff) << 16 |
(s.ip3.to<unsigned>() & 0xff) << 8 |
@@ -42,4 +47,4 @@ module_status udp::initialize()
return error(tr("Can't bind socket: %1").arg(outSocket.errorString()));
}
-OPENTRACK_DECLARE_PROTOCOL(udp, FTNControls, udpDll)
+OPENTRACK_DECLARE_PROTOCOL(udp, FTNControls, udp_sender_dll)
diff --git a/proto-udp/ftnoir_protocol_ftn.h b/proto-udp/ftnoir_protocol_ftn.h
index c0454699..ca387a37 100644
--- a/proto-udp/ftnoir_protocol_ftn.h
+++ b/proto-udp/ftnoir_protocol_ftn.h
@@ -11,10 +11,10 @@
#pragma once
#include "ui_ftnoir_ftncontrols.h"
-#include <QUdpSocket>
#include "api/plugin-api.hpp"
#include "options/options.hpp"
using namespace options;
+#include <QUdpSocket>
struct settings : opts {
value<int> ip1, ip2, ip3, ip4, port;
@@ -35,14 +35,14 @@ class udp : public QObject, public IProtocol
public:
udp();
module_status initialize() override;
- void pose(const double *headpose);
- QString game_name() {
- return otr_tr("UDP over network");
- }
+ void pose(const double *headpose, const double*) override;
+ QString game_name() override { return tr("UDP over network"); }
private:
QUdpSocket outSocket;
settings s;
+ mutable QMutex lock;
+
QHostAddress dest_ip { 127u << 24 | 1u };
unsigned short dest_port = 65535;
@@ -57,8 +57,8 @@ class FTNControls: public IProtocolDialog
public:
FTNControls();
- void register_protocol(IProtocol *) {}
- void unregister_protocol() {}
+ void register_protocol(IProtocol *) override {}
+ void unregister_protocol() override {}
private:
Ui::UICFTNControls ui;
settings s;
@@ -67,9 +67,10 @@ private slots:
void doCancel();
};
-class udpDll : public Metadata
+class udp_sender_dll : public Metadata
{
-public:
- QString name() { return otr_tr("UDP over network"); }
- QIcon icon() { return QIcon(":/images/opentrack.png"); }
+ Q_OBJECT
+
+ QString name() override { return tr("UDP over network"); }
+ QIcon icon() override { return QIcon(":/images/opentrack.png"); }
};
diff --git a/proto-udp/lang/de_DE.ts b/proto-udp/lang/de_DE.ts
new file mode 100644
index 00000000..442d8683
--- /dev/null
+++ b/proto-udp/lang/de_DE.ts
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>UICFTNControls</name>
+ <message>
+ <source>UDP protocol settings</source>
+ <translation>UDP-Protokolleinstellungen</translation>
+ </message>
+ <message>
+ <source>Remote IP address</source>
+ <translation>Entfernte IP-Adresse</translation>
+ </message>
+ <message>
+ <source>Port</source>
+ <translation>Port</translation>
+ </message>
+</context>
+<context>
+ <name>udp</name>
+ <message>
+ <source>Can&apos;t bind socket: %1</source>
+ <translation>Kann nicht an Socket binden: %1</translation>
+ </message>
+ <message>
+ <source>UDP over network</source>
+ <translation>UDP über Netzwerk</translation>
+ </message>
+</context>
+<context>
+ <name>udp_sender_dll</name>
+ <message>
+ <source>UDP over network</source>
+ <translation>UDP über Netzwerk</translation>
+ </message>
+</context>
+</TS>
diff --git a/proto-udp/lang/nl_NL.ts b/proto-udp/lang/nl_NL.ts
index 069a0a74..d955c98f 100644
--- a/proto-udp/lang/nl_NL.ts
+++ b/proto-udp/lang/nl_NL.ts
@@ -22,5 +22,16 @@
<source>Can&apos;t bind socket: %1</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>UDP over network</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>udp_sender_dll</name>
+ <message>
+ <source>UDP over network</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
</TS>
diff --git a/proto-udp/lang/ru_RU.ts b/proto-udp/lang/ru_RU.ts
index 486d5707..b1ea5201 100644
--- a/proto-udp/lang/ru_RU.ts
+++ b/proto-udp/lang/ru_RU.ts
@@ -22,5 +22,16 @@
<source>Can&apos;t bind socket: %1</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>UDP over network</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>udp_sender_dll</name>
+ <message>
+ <source>UDP over network</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
</TS>
diff --git a/proto-udp/lang/stub.ts b/proto-udp/lang/stub.ts
index d415ef0e..ca1bc409 100644
--- a/proto-udp/lang/stub.ts
+++ b/proto-udp/lang/stub.ts
@@ -22,5 +22,16 @@
<source>Can&apos;t bind socket: %1</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>UDP over network</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>udp_sender_dll</name>
+ <message>
+ <source>UDP over network</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
</TS>
diff --git a/proto-udp/lang/zh_CN.ts b/proto-udp/lang/zh_CN.ts
index d415ef0e..b2a750b8 100644
--- a/proto-udp/lang/zh_CN.ts
+++ b/proto-udp/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>UICFTNControls</name>
<message>
@@ -22,5 +22,16 @@
<source>Can&apos;t bind socket: %1</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>UDP over network</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>udp_sender_dll</name>
+ <message>
+ <source>UDP over network</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
</TS>
diff --git a/proto-vjoystick/CMakeLists.txt b/proto-vjoystick/CMakeLists.txt
index 41af2883..5b6a1f07 100644
--- a/proto-vjoystick/CMakeLists.txt
+++ b/proto-vjoystick/CMakeLists.txt
@@ -1,4 +1,4 @@
-if(WIN32)
+if(WIN32 AND opentrack-intel)
set(SDK_VJOYSTICK "" CACHE PATH "vjoystick SDK path")
if(SDK_VJOYSTICK)
if(opentrack-64bit)
@@ -11,6 +11,6 @@ if(WIN32)
otr_module(proto-vjoy)
target_link_libraries(opentrack-proto-vjoy ${lib})
target_include_directories(opentrack-proto-vjoy SYSTEM PUBLIC "${SDK_VJOYSTICK}/inc")
- install(FILES "${dll}" DESTINATION ${opentrack-hier-pfx})
+ install(FILES "${dll}" DESTINATION ${opentrack-libexec})
endif()
endif()
diff --git a/proto-vjoystick/lang/nl_NL.ts b/proto-vjoystick/lang/nl_NL.ts
index f9d754a5..056563f7 100644
--- a/proto-vjoystick/lang/nl_NL.ts
+++ b/proto-vjoystick/lang/nl_NL.ts
@@ -4,11 +4,66 @@
<context>
<name>vjoystick</name>
<message>
- <source>VJoy</source>
+ <source>vjoystick won&apos;t work without the driver installed.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Go to the &lt;a href=&quot;http://vjoystick.sourceforge.net/site/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;vjoystick&lt;/span&gt;&lt;/a&gt; project site or &lt;a href=&quot;https://sourceforge.net/projects/vjoystick/files/latest/download&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;download directly&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <source>Download the driver</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Visit project site</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Device missing. Add joystick #1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>vjoystick driver problem</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Driver problem.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>vjoystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>BUG: handle leak.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Virtual joystick already in use.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error #%1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>driver/SDK version mismatch (dll 0x%1, driver 0x%2)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Go to the &lt;a href=&quot;https://github.com/jshafer817/vJoy&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;vJoy&lt;/span&gt;&lt;/a&gt; project site or &lt;a href=&quot;https://github.com/jshafer817/vJoy/releases/tag/v2.1.9.1&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;download directly&lt;/span&gt;&lt;/a&gt; for Windows 10 and 11.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>vjoystick_metadata</name>
+ <message>
+ <source>Joystick emulation -- vjoystick</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-vjoystick/lang/ru_RU.ts b/proto-vjoystick/lang/ru_RU.ts
index c6834f9d..4c9aacd3 100644
--- a/proto-vjoystick/lang/ru_RU.ts
+++ b/proto-vjoystick/lang/ru_RU.ts
@@ -4,11 +4,66 @@
<context>
<name>vjoystick</name>
<message>
- <source>VJoy</source>
+ <source>vjoystick won&apos;t work without the driver installed.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Go to the &lt;a href=&quot;http://vjoystick.sourceforge.net/site/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;vjoystick&lt;/span&gt;&lt;/a&gt; project site or &lt;a href=&quot;https://sourceforge.net/projects/vjoystick/files/latest/download&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;download directly&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <source>Download the driver</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Visit project site</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Device missing. Add joystick #1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>vjoystick driver problem</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Driver problem.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>vjoystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>BUG: handle leak.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Virtual joystick already in use.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error #%1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>driver/SDK version mismatch (dll 0x%1, driver 0x%2)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Go to the &lt;a href=&quot;https://github.com/jshafer817/vJoy&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;vJoy&lt;/span&gt;&lt;/a&gt; project site or &lt;a href=&quot;https://github.com/jshafer817/vJoy/releases/tag/v2.1.9.1&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;download directly&lt;/span&gt;&lt;/a&gt; for Windows 10 and 11.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>vjoystick_metadata</name>
+ <message>
+ <source>Joystick emulation -- vjoystick</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-vjoystick/lang/stub.ts b/proto-vjoystick/lang/stub.ts
index 1a85a52f..8ec5c042 100644
--- a/proto-vjoystick/lang/stub.ts
+++ b/proto-vjoystick/lang/stub.ts
@@ -4,11 +4,66 @@
<context>
<name>vjoystick</name>
<message>
- <source>VJoy</source>
+ <source>vjoystick won&apos;t work without the driver installed.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Go to the &lt;a href=&quot;http://vjoystick.sourceforge.net/site/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;vjoystick&lt;/span&gt;&lt;/a&gt; project site or &lt;a href=&quot;https://sourceforge.net/projects/vjoystick/files/latest/download&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;download directly&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <source>Download the driver</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Visit project site</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Device missing. Add joystick #1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>vjoystick driver problem</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Driver problem.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>vjoystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>BUG: handle leak.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Virtual joystick already in use.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error #%1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>driver/SDK version mismatch (dll 0x%1, driver 0x%2)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Go to the &lt;a href=&quot;https://github.com/jshafer817/vJoy&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;vJoy&lt;/span&gt;&lt;/a&gt; project site or &lt;a href=&quot;https://github.com/jshafer817/vJoy/releases/tag/v2.1.9.1&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;download directly&lt;/span&gt;&lt;/a&gt; for Windows 10 and 11.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>vjoystick_metadata</name>
+ <message>
+ <source>Joystick emulation -- vjoystick</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-vjoystick/lang/zh_CN.ts b/proto-vjoystick/lang/zh_CN.ts
index 1a85a52f..95192e1d 100644
--- a/proto-vjoystick/lang/zh_CN.ts
+++ b/proto-vjoystick/lang/zh_CN.ts
@@ -1,14 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>vjoystick</name>
<message>
- <source>VJoy</source>
+ <source>vjoystick won&apos;t work without the driver installed.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Go to the &lt;a href=&quot;http://vjoystick.sourceforge.net/site/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;vjoystick&lt;/span&gt;&lt;/a&gt; project site or &lt;a href=&quot;https://sourceforge.net/projects/vjoystick/files/latest/download&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;download directly&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <source>Download the driver</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Visit project site</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Device missing. Add joystick #1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>vjoystick driver problem</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Driver problem.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>vjoystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>BUG: handle leak.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Virtual joystick already in use.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error #%1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>driver/SDK version mismatch (dll 0x%1, driver 0x%2)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Go to the &lt;a href=&quot;https://github.com/jshafer817/vJoy&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;vJoy&lt;/span&gt;&lt;/a&gt; project site or &lt;a href=&quot;https://github.com/jshafer817/vJoy/releases/tag/v2.1.9.1&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;download directly&lt;/span&gt;&lt;/a&gt; for Windows 10 and 11.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>vjoystick_metadata</name>
+ <message>
+ <source>Joystick emulation -- vjoystick</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-vjoystick/vjoystick.cpp b/proto-vjoystick/vjoystick.cpp
index e5e18157..292e8259 100644
--- a/proto-vjoystick/vjoystick.cpp
+++ b/proto-vjoystick/vjoystick.cpp
@@ -26,7 +26,7 @@
#define OPENTRACK_VJOYSTICK_ID 1
-const unsigned char handle::axis_ids[6] =
+const unsigned char vjoystick::axis_ids[6] =
{
HID_USAGE_X,
HID_USAGE_Y,
@@ -39,7 +39,7 @@ const unsigned char handle::axis_ids[6] =
// HID_USAGE_WHL,
};
-static const double val_minmax[6] =
+static constexpr double val_minmax[6] =
{
50,
50,
@@ -49,77 +49,102 @@ static const double val_minmax[6] =
180
};
-void handle::init()
+bool vjoystick::init()
{
+ if (!AcquireVJD(OPENTRACK_VJOYSTICK_ID))
+ return false;
+
+ unsigned cnt = 0;
+
for (unsigned i = 0; i < axis_count; i++)
{
- if (!GetVJDAxisExist(OPENTRACK_VJOYSTICK_ID, axis_ids[i]))
+ bool status = true;
+
+ status &= !!GetVJDAxisExist(OPENTRACK_VJOYSTICK_ID, axis_ids[i]);
+ status &= !!GetVJDAxisMin(OPENTRACK_VJOYSTICK_ID, axis_ids[i], &axis_min[i]);
+ status &= !!GetVJDAxisMax(OPENTRACK_VJOYSTICK_ID, axis_ids[i], &axis_max[i]);
+
+ if (!status)
{
- // avoid floating point division by zero
axis_min[i] = 0;
- axis_max[i] = 1;
- continue;
+ axis_max[i] = 0;
}
- GetVJDAxisMin(OPENTRACK_VJOYSTICK_ID, axis_ids[i], &axis_min[i]);
- GetVJDAxisMax(OPENTRACK_VJOYSTICK_ID, axis_ids[i], &axis_max[i]);
- }
- (void) ResetVJD(OPENTRACK_VJOYSTICK_ID);
-}
-
-handle::handle()
-{
- const bool ret = AcquireVJD(OPENTRACK_VJOYSTICK_ID);
- if (!ret)
- {
- if (!isVJDExists(OPENTRACK_VJOYSTICK_ID))
- joy_state = state_notent;
else
- joy_state = state_fail;
- }
- else
- {
- joy_state = state_success;
- init();
+ cnt++;
}
-}
-handle::~handle()
-{
- if (joy_state == state_success)
+ if (!cnt)
{
- (void) RelinquishVJD(OPENTRACK_VJOYSTICK_ID);
- joy_state = state_fail;
+ RelinquishVJD(OPENTRACK_VJOYSTICK_ID);
+ return false;
}
+ else
+ return true;
}
-LONG handle::to_axis_value(unsigned axis_id, double val)
+int vjoystick::to_axis_value(unsigned axis_id, double val) const
{
const double minmax = val_minmax[axis_id];
const double min = axis_min[axis_id];
const double max = axis_max[axis_id];
- return LONG(clamp((val+minmax) * max / (2*minmax) - min, min, max));
+ return (int)(std::clamp((val+minmax) * max / (2*minmax) - min, min, max));
}
-vjoystick_proto::vjoystick_proto()
+vjoystick::vjoystick() = default;
+vjoystick::~vjoystick()
{
+ if (status)
+ RelinquishVJD(OPENTRACK_VJOYSTICK_ID);
}
-vjoystick_proto::~vjoystick_proto()
+module_status vjoystick::initialize()
{
-}
+ QString msg;
+
+ if (!vJoyEnabled())
+ msg = tr("vjoystick won't work without the driver installed.");
+#if 0
+ else if (WORD VerDll, VerDrv; !DriverMatch(&VerDll, &VerDrv))
+ msg = tr("driver/SDK version mismatch (dll 0x%1, driver 0x%2)")
+ .arg(QString::number(VerDll, 16), QString::number(VerDrv, 16));
+#endif
+ else
+ {
+ int code;
+ switch (code = GetVJDStatus(OPENTRACK_VJOYSTICK_ID))
+ {
+ case VJD_STAT_OWN:
+ msg = tr("BUG: handle leak.");
+ break;
+ case VJD_STAT_BUSY:
+ msg = tr("Virtual joystick already in use.");
+ break;
+ case VJD_STAT_MISS:
+ msg = tr("Device missing. Add joystick #1.");
+ break;
+ case VJD_STAT_UNKN:
+ msg = tr("Unknown error.");
+ break;
+ default:
+ msg = tr("Unknown error #%1.").arg(code);
+ break;
+ case VJD_STAT_FREE:
+ // we're good
+ status = true;
+ break;
+ }
+ }
-module_status vjoystick_proto::initialize()
-{
- if (h.get_state() != state_success)
+ if (!status)
{
QMessageBox msgbox;
msgbox.setIcon(QMessageBox::Critical);
- msgbox.setText(otr_tr("vjoystick driver missing"));
- msgbox.setInformativeText(otr_tr("vjoystick won't work without the driver installed."));
+ msgbox.setText(tr("vjoystick driver problem"));
+ msgbox.setInformativeText(msg);
- QPushButton* driver_button = msgbox.addButton(otr_tr("Download the driver"), QMessageBox::ActionRole);
- QPushButton* project_site_button = msgbox.addButton(otr_tr("Visit project site"), QMessageBox::ActionRole);
+ QPushButton* driver_button = msgbox.addButton(tr("Download the driver"), QMessageBox::ActionRole);
+ QPushButton* project_site_button = msgbox.addButton(tr("Visit project site"), QMessageBox::ActionRole);
msgbox.addButton(QMessageBox::Close);
(void) msgbox.exec();
@@ -136,28 +161,32 @@ module_status vjoystick_proto::initialize()
}
}
- switch (h.get_state())
- {
- case state_notent:
- return error(_("vjoystick not installed or disabled"));
- case state_fail:
- return error(_("can't initialize vjoystick"));
- case state_success:
- return status_ok();
- default:
- return error(_("unknown error"));
- }
+ if (!status)
+ return error(tr("Driver problem."));
+ else
+ return {};
}
-void vjoystick_proto::pose(const double *pose)
+void vjoystick::pose(const double *pose, const double*)
{
- if (h.get_state() != state_success)
+ if (first_run)
+ {
+ status = init();
+ //status &= !!ResetVJD(OPENTRACK_VJOYSTICK_ID);
+ first_run = false;
+ }
+
+ if (!status)
return;
- for (unsigned i = 0; i < handle::axis_count; i++)
+ for (unsigned i = 0; i < vjoystick::axis_count; i++)
{
- SetAxis(h.to_axis_value(i, pose[i]), OPENTRACK_VJOYSTICK_ID, handle::axis_ids[i]);
+ if (axis_min[i] == axis_max[i])
+ continue;
+
+ int val = to_axis_value(i, pose[i]);
+ SetAxis(val, OPENTRACK_VJOYSTICK_ID, vjoystick::axis_ids[i]);
}
}
-OPENTRACK_DECLARE_PROTOCOL(vjoystick_proto, vjoystick_dialog, vjoystick_metadata)
+OPENTRACK_DECLARE_PROTOCOL(vjoystick, vjoystick_dialog, vjoystick_metadata)
diff --git a/proto-vjoystick/vjoystick.h b/proto-vjoystick/vjoystick.h
index 72dde0f0..82ebd3e6 100644
--- a/proto-vjoystick/vjoystick.h
+++ b/proto-vjoystick/vjoystick.h
@@ -8,46 +8,33 @@
#pragma once
#include "ui_vjoystick.h"
#include "api/plugin-api.hpp"
-#include "compat/macros.hpp"
-#include <windows.h>
-
-enum state : signed char
+enum status
{
- state_notent = -1,
- state_fail = -2,
- state_success = 1,
};
-class handle final
+class vjoystick : public TR, public IProtocol
{
+ Q_OBJECT
+
public:
- static constexpr unsigned axis_count = 6;
- static const unsigned char axis_ids[axis_count];
+ vjoystick();
+ ~vjoystick() override;
+ module_status initialize() override;
+ void pose(const double* headpose, const double*) override;
+ QString game_name() override { return tr("Virtual joystick"); }
private:
- state joy_state;
- LONG axis_min[6];
- LONG axis_max[6];
+ long axis_min[6] {};
+ long axis_max[6] {};
+ [[nodiscard]] bool init();
+ int to_axis_value(unsigned axis_id, double val) const;
- void init();
-public:
- handle();
- ~handle();
- state get_state() { return joy_state; }
- LONG to_axis_value(unsigned axis_id, double val);
-};
+ static constexpr unsigned axis_count = 6;
+ static const unsigned char axis_ids[axis_count];
-class vjoystick_proto : public IProtocol
-{
- handle h;
-public:
- vjoystick_proto();
- ~vjoystick_proto() override;
- module_status initialize() override;
- void pose( const double *headpose ) override;
- QString game_name() override { return otr_tr("Virtual joystick"); }
-private:
+ bool status = false;
+ bool first_run = true;
};
class vjoystick_dialog final : public IProtocolDialog
@@ -64,7 +51,8 @@ private:
class vjoystick_metadata : public Metadata
{
-public:
- QString name() { return otr_tr("Joystick emulation -- vjoystick"); }
- QIcon icon() { return QIcon(":/images/vjoystick.png"); }
+ Q_OBJECT
+
+ QString name() override { return tr("Joystick emulation -- vjoystick"); }
+ QIcon icon() override { return QIcon(":/images/vjoystick.png"); }
};
diff --git a/proto-vjoystick/vjoystick.ui b/proto-vjoystick/vjoystick.ui
index 6e6a2b33..8092898b 100644
--- a/proto-vjoystick/vjoystick.ui
+++ b/proto-vjoystick/vjoystick.ui
@@ -2,9 +2,6 @@
<ui version="4.0">
<class>vjoystick</class>
<widget class="QWidget" name="vjoystick">
- <property name="windowModality">
- <enum>Qt::NonModal</enum>
- </property>
<property name="geometry">
<rect>
<x>0</x>
@@ -14,23 +11,17 @@
</rect>
</property>
<property name="windowTitle">
- <string>VJoy</string>
+ <string>vjoystick</string>
</property>
<property name="windowIcon">
<iconset resource="vjoystick.qrc">
<normaloff>:/images/vjoystick.png</normaloff>:/images/vjoystick.png</iconset>
</property>
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="autoFillBackground">
- <bool>false</bool>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Go to the &lt;a href=&quot;http://vjoystick.sourceforge.net/site/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;vjoystick&lt;/span&gt;&lt;/a&gt; project site or &lt;a href=&quot;https://sourceforge.net/projects/vjoystick/files/latest/download&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;download directly&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Go to the &lt;a href=&quot;https://github.com/jshafer817/vJoy&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;vJoy&lt;/span&gt;&lt;/a&gt; project site or &lt;a href=&quot;https://github.com/jshafer817/vJoy/releases/tag/v2.1.9.1&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;download directly&lt;/span&gt;&lt;/a&gt; for Windows 10 and 11.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -40,10 +31,10 @@
</property>
</widget>
</item>
- <item>
+ <item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
- <set>QDialogButtonBox::Ok</set>
+ <set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
diff --git a/proto-vjoystick/vjoystick_dialog.cpp b/proto-vjoystick/vjoystick_dialog.cpp
index 461230a6..382d8fb3 100644
--- a/proto-vjoystick/vjoystick_dialog.cpp
+++ b/proto-vjoystick/vjoystick_dialog.cpp
@@ -1,8 +1,11 @@
#include "vjoystick.h"
#include "api/plugin-api.hpp"
+#include <QDialogButtonBox>
+
vjoystick_dialog::vjoystick_dialog()
{
ui.setupUi(this);
connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &QWidget::close);
+ connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close);
}
diff --git a/proto-wine/CMakeLists.txt b/proto-wine/CMakeLists.txt
index 629f47f3..ff4932cc 100644
--- a/proto-wine/CMakeLists.txt
+++ b/proto-wine/CMakeLists.txt
@@ -1,27 +1,37 @@
if(NOT WIN32)
- set(SDK_WINE_PREFIX "" CACHE PATH "Wine install prefix")
- set(SDK_WINE_NO_WRAPPER FALSE CACHE BOOL "disable Wine wrapper -- use Wine only for X-Plane")
- if(SDK_WINE_PREFIX)
+ set(SDK_WINE "" CACHE BOOL "Build for Wine")
+ set(no-wrapper FALSE)
+ if(NOT SDK_WINE AND SDK_XPLANE)
+ set(no-wrapper TRUE)
+ endif()
+ if(SDK_WINE OR no-wrapper)
+ if(no-wrapper)
+ add_definitions(-DOTR_WINE_NO_WRAPPER)
+ endif()
otr_module(proto-wine)
- target_link_libraries(opentrack-proto-wine opentrack-csv)
- if(NOT SDK_WINE_NO_WRAPPER)
+ if(NOT no-wrapper)
+ target_link_libraries(opentrack-proto-wine opentrack-csv)
set(my-rt -lrt)
if(APPLE)
set(my-rt)
endif()
file(GLOB wine-deps "${CMAKE_CURRENT_SOURCE_DIR}/*.cxx")
- install(FILES ${wine-deps} DESTINATION "${opentrack-doc-src-pfx}/proto-wine")
+ #install(FILES ${wine-deps} DESTINATION "${opentrack-src}/proto-wine")
+ set(winegxx-multilib "-m32")
+ if (NOT OPENTRACK_WINE_ARCH STREQUAL "")
+ set(winegxx-multilib "${OPENTRACK_WINE_ARCH}")
+ endif()
add_custom_command(
OUTPUT opentrack-wrapper-wine.exe.so
DEPENDS ${wine-deps}
- COMMAND ${SDK_WINE_PREFIX}/bin/wineg++ -g -DNOMINMAX -O2 -m32 -std=c++14 -o
+ COMMAND wineg++ -mconsole -g -DNOMINMAX -O2 ${winegxx-multilib} -std=c++17 -fPIC -o
opentrack-wrapper-wine.exe -I "${CMAKE_SOURCE_DIR}" -I "${CMAKE_BINARY_DIR}"
- ${wine-deps}
+ ${wine-deps} -Wall -Wextra -Wpedantic
${my-rt})
- add_custom_target(wine-wrapper ALL DEPENDS opentrack-wrapper-wine.exe.so)
+ add_custom_target(wine-wrapper DEPENDS opentrack-wrapper-wine.exe.so)
add_dependencies(opentrack-proto-wine wine-wrapper)
add_dependencies(wine-wrapper opentrack-compat)
- install(FILES "${CMAKE_CURRENT_BINARY_DIR}/opentrack-wrapper-wine.exe.so" DESTINATION ${opentrack-hier-pfx})
+ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/opentrack-wrapper-wine.exe.so" DESTINATION ${opentrack-libexec})
endif()
endif()
endif()
diff --git a/proto-wine/ftnoir_protocol_wine.cpp b/proto-wine/ftnoir_protocol_wine.cpp
index 42fb4896..bae02b06 100644
--- a/proto-wine/ftnoir_protocol_wine.cpp
+++ b/proto-wine/ftnoir_protocol_wine.cpp
@@ -1,48 +1,52 @@
#include "ftnoir_protocol_wine.h"
+#include <qprocess.h>
+#ifndef OTR_WINE_NO_WRAPPER
+# include "csv/csv.h"
+#endif
+
+#include <cstring>
+#include <cmath>
+
#include <QString>
-#include <QStringList>
-#include <QCoreApplication>
-#include <string.h>
-#include <math.h>
-#include <sys/mman.h>
-#include <sys/stat.h> /* For mode constants */
-#include <fcntl.h> /* For O_* constants */
-#include "csv/csv.h"
-#include "compat/macros.hpp"
-#include "compat/library-path.hpp"
-
-wine::wine() : lck_shm(WINE_SHM_NAME, WINE_MTX_NAME, sizeof(WineSHM)), shm(NULL), gameid(0)
-{
- if (lck_shm.success()) {
- shm = (WineSHM*) lck_shm.ptr();
- memset(shm, 0, sizeof(*shm));
- }
- static const QString library_path(QCoreApplication::applicationDirPath() + OPENTRACK_LIBRARY_PATH);
- wrapper.setWorkingDirectory(QCoreApplication::applicationDirPath());
- wrapper.start("wine", QStringList() << (library_path + "opentrack-wrapper-wine.exe.so"));
-}
+#include <QDebug>
+
+#include "proton.h"
+
+wine::wine() = default;
wine::~wine()
{
+#ifndef OTR_WINE_NO_WRAPPER
+ bool exit = false;
if (shm) {
shm->stop = true;
- wrapper.waitForFinished(100);
+ exit = wrapper.waitForFinished(100);
+ if (exit)
+ qDebug() << "proto/wine: wrapper exit code" << wrapper.exitCode();
+ }
+ if (!exit)
+ {
+ if (wrapper.state() != QProcess::NotRunning)
+ wrapper.kill();
+ wrapper.waitForFinished(1000);
}
- wrapper.close();
+#endif
//shm_unlink("/" WINE_SHM_NAME);
}
-void wine::pose( const double *headpose )
+void wine::pose(const double *headpose, const double*)
{
if (shm)
{
lck_shm.lock();
for (int i = 3; i < 6; i++)
- shm->data[i] = headpose[i] / (180 / M_PI );
+ shm->data[i] = (headpose[i] * M_PI) / 180;
for (int i = 0; i < 3; i++)
shm->data[i] = headpose[i] * 10;
+#ifndef OTR_WINE_NO_WRAPPER
if (shm->gameid != gameid)
{
+ //qDebug() << "proto/wine: looking up gameData";
QString gamename;
QMutexLocker foo(&game_name_mutex);
/* only EZCA for FSX requires dummy process, and FSX doesn't work on Linux */
@@ -51,16 +55,152 @@ void wine::pose( const double *headpose )
gameid = shm->gameid2 = shm->gameid;
connected_game = gamename;
}
+#endif
lck_shm.unlock();
}
}
module_status wine::initialize()
{
+#ifndef OTR_WINE_NO_WRAPPER
+ static const QString library_path(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH);
+
+ /////////////////////////
+ // determine wine path //
+ /////////////////////////
+ QString wine_path = "wine";
+
+ if (s.variant_wine) {
+ // NORMAL WINE
+
+ // resolve combo box
+ if (s.wine_select_path().toString() != "WINE") {
+ // if we are not supposed to use system wine then:
+ if (s.wine_select_path().toString() != "CUSTOM") {
+ // if we don't have a custom path then change the wine_path to the path corresponding to the selected version
+ wine_path = s.wine_select_path().toString();
+ }
+ else if (!s.wine_custom_path->isEmpty()) {
+ // if we do have a custom path and it is not empty then
+ wine_path = s.wine_custom_path;
+ }
+ }
+
+ // parse tilde if present
+ if (wine_path[0] == '~')
+ wine_path = qgetenv("HOME") + wine_path.mid(1);
+ }
+ else if (s.variant_proton)
+ {
+ // PROTON
+
+ wine_path = s.proton_path().toString() + "/bin/wine";
+ }
+ qDebug() << "proto/wine: wine_path:" << wine_path;
+
+
+ /////////////////////////////////////
+ // determine environment variables //
+ /////////////////////////////////////
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+
+ // if proton is used setup proton environment
+ if (s.variant_proton)
+ {
+ auto [proton_env, env_error_string, env_success] = make_steam_environ(s.proton_path().toString());
+ env = proton_env;
+
+ if (!env_success)
+ return error(env_error_string);
+ }
+
+ // determine wineprefix
+ if (s.variant_proton && s.variant_proton_steamplay) {
+ // wine prefix is dependend on steam
+
+ if (s.proton_appid == 0)
+ return error(tr("Must specify application id for Proton (Steam Play)"));
+
+ auto [prefix, error_string, success] = make_wineprefix(s.proton_appid);
+ qDebug() << "proto/wine: wineprefix:" << prefix;
+ env.insert("WINEPREFIX", prefix);
+
+ if (!success)
+ return error(error_string);
+ }
+ else {
+ // wine prefix was supplied via path
+
+ QString wineprefix = "";
+
+ // check if prefix was supplied via wine
+ if (s.variant_wine && !s.wineprefix->isEmpty())
+ wineprefix = s.wineprefix;
+
+ // check if prefix was supplied via proton
+ if (s.variant_proton_external && !s.protonprefix->isEmpty())
+ wineprefix = s.protonprefix;
+
+ // check if the user specified a prefix anywhere
+ if (wineprefix.isEmpty())
+ return error(tr("Prefix has not been defined!").arg(wineprefix));
+
+ // handle tilde
+ if (wineprefix[0] == '~')
+ wineprefix = qgetenv("HOME") + wineprefix.mid(1);
+
+ // return error if relative path is given
+ if (wineprefix[0] != '/')
+ return error(tr("Wine prefix must be an absolute path (given '%1')").arg(wineprefix));
+
+ qDebug() << "proto/wine: wineprefix:" << wineprefix;
+
+ env.insert("WINEPREFIX", wineprefix);
+ }
+
+ // ESYNC and FSYNC
+ if (s.esync)
+ env.insert("WINEESYNC", "1");
+ if (s.fsync)
+ env.insert("WINEFSYNC", "1");
+
+ // Headtracking Protocol
+ env.insert("OTR_WINE_PROTO", QString::number(s.protocol+1));
+
+
+ ////////////////////////////////
+ // launch the wrapper program //
+ ////////////////////////////////
+
+ wrapper.setProcessEnvironment(env);
+ wrapper.setWorkingDirectory(OPENTRACK_BASE_PATH);
+ wrapper.start(wine_path, { library_path + "opentrack-wrapper-wine.exe.so" });
+ wrapper.waitForStarted();
+ if (wrapper.state() == QProcess::ProcessState::NotRunning) {
+ return error(tr("Failed to start Wine! Make sure the binary is set correctly."));
+ }
+#endif
+
+ if (lck_shm.success())
+ {
+ shm = (WineSHM*) lck_shm.ptr();
+ memset(shm, 0, sizeof(*shm));
+
+ qDebug() << "proto/wine: shm success";
+
+ // display "waiting for game message" (overwritten once a game is detected)
+#ifndef OTR_WINE_NO_WRAPPER
+ connected_game = "waiting for game...";
+#endif
+ }
+ else {
+ qDebug() << "proto/wine: shm no success";
+ }
+
if (lck_shm.success())
return status_ok();
else
- return error(otr_tr("Can't open shared memory mapping"));
+ return error(tr("Can't open shared memory mapping"));
}
-OPENTRACK_DECLARE_PROTOCOL(wine, FTControls, wineDll)
+OPENTRACK_DECLARE_PROTOCOL(wine, FTControls, wine_metadata)
diff --git a/proto-wine/ftnoir_protocol_wine.h b/proto-wine/ftnoir_protocol_wine.h
index 0f300ff3..718699ac 100644
--- a/proto-wine/ftnoir_protocol_wine.h
+++ b/proto-wine/ftnoir_protocol_wine.h
@@ -1,43 +1,77 @@
#pragma once
-#include "ui_ftnoir_winecontrols.h"
-#include <QMessageBox>
-#include <QLibrary>
-#include <QProcess>
-#include <QDebug>
-#include <QMutex>
-#include <QMutexLocker>
-#include <QFile>
#include "api/plugin-api.hpp"
#include "compat/shm.h"
#include "wine-shm.h"
-class wine : public IProtocol
+#include "ui_ftnoir_winecontrols.h"
+
+#include "options/options.hpp"
+using namespace options;
+
+#include <QMutex>
+#include <QProcess>
+#include <QString>
+#include <QVariant>
+
+#include <QDebug>
+
+struct settings : opts
{
+ settings() : opts{"proto-wine"} {}
+ value<bool> variant_wine{b, "variant-wine", true },
+ variant_proton{b, "variant-proton", false },
+ variant_proton_steamplay{b, "variant-proton-steamplay", true },
+ variant_proton_external{b, "variant-proton-external", false },
+ fsync{b, "fsync", true},
+ esync{b, "esync", true};
+
+ value<int> proton_appid{b, "proton-appid", 0};
+ value<QVariant> proton_path{b, "proton-version", {} };
+ value<QVariant> wine_select_path{b, "wine-select-version", {"WINE"}};
+ value<QString> wine_custom_path{b, "wine-custom-version", ""};
+ value<QString> wineprefix{b, "wineprefix", "~/.wine/"};
+ value<QString> protonprefix{b, "protonprefix", ""};
+ value<int> protocol{b, "protocol", 2};
+};
+
+class wine : TR, public IProtocol
+{
+ Q_OBJECT
+
public:
wine();
~wine() override;
module_status initialize() override;
- void pose(const double* headpose) override;
+ void pose(const double* headpose, const double*) override;
QString game_name() override
{
+#ifndef OTR_WINE_NO_WRAPPER
QMutexLocker foo(&game_name_mutex);
return connected_game;
+#else
+ return QStringLiteral("X-Plane");
+#endif
}
private:
- shm_wrapper lck_shm;
- WineSHM* shm;
+ shm_wrapper lck_shm { WINE_SHM_NAME, WINE_MTX_NAME, sizeof(WineSHM) };
+ WineSHM* shm = nullptr;
+ settings s;
+
+#ifndef OTR_WINE_NO_WRAPPER
QProcess wrapper;
- int gameid;
+ int gameid = 0;
QString connected_game;
QMutex game_name_mutex;
+#endif
};
class FTControls: public IProtocolDialog
{
Q_OBJECT
+
public:
FTControls();
void register_protocol(IProtocol *) override {}
@@ -45,15 +79,31 @@ public:
private:
Ui::UICFTControls ui;
+ settings s;
private slots:
+ void onWinePathComboUpdated();
+ void onRadioButtonsChanged();
+
+ void doBrowseWine();
+ void doBrowseWinePrefix();
+
+ void doBrowseProtonPrefix();
+
void doOK();
void doCancel();
};
-class wineDll : public Metadata
+class wine_metadata : public Metadata
{
+ Q_OBJECT
+
public:
- QString name() override { return QString("Wine -- Windows layer for Unix"); }
+#ifndef OTR_WINE_NO_WRAPPER
+ QString name() override { return tr("Wine -- Windows layer for Unix"); }
QIcon icon() override { return QIcon(":/images/wine.png"); }
+#else
+ QString name() override { return tr("X-Plane"); }
+ QIcon icon() override { return {}; }
+#endif
};
diff --git a/proto-wine/ftnoir_protocol_wine_dialog.cpp b/proto-wine/ftnoir_protocol_wine_dialog.cpp
index a388df70..2b7d08e0 100644
--- a/proto-wine/ftnoir_protocol_wine_dialog.cpp
+++ b/proto-wine/ftnoir_protocol_wine_dialog.cpp
@@ -1,19 +1,232 @@
#include "ftnoir_protocol_wine.h"
#include <QDebug>
+#include <QFileDialog>
+#include <QDir>
+#include <QDirIterator>
+#include <qcombobox.h>
+#include <qdebug.h>
+#include <qdir.h>
+#include <qradiobutton.h>
+
#include "api/plugin-api.hpp"
+#include "options/tie.hpp"
+
+/*
+ * 0: path to the directory with wine versions
+ * 1: path from a wine version to the exectuable
+ * 2: name of the application using the wine versions
+ */
+static const char* wine_paths[][3] = {
+ {"/.local/share/lutris/runners/wine/", "/bin/wine", "Lutris"},
+ {"/.var/app/net.lutris.Lutris/data/lutris/runners/wine/", "/bin/wine", "Flatpak Lutris"}
+};
+
+static const char* proton_paths[][2] = {
+ {"/.steam/steam/steamapps/common", "Proton*"},
+ {"/.steam/root/compatibilitytools.d", "*"},
+ {"/.local/share/Steam/steamapps/common", "Proton*"},
+ {"/.local/share/Steam/compatibilitytools.d", "*"},
+};
FTControls::FTControls()
{
- ui.setupUi( this );
- connect(ui.btnOK, SIGNAL(clicked()), this, SLOT(doOK()));
- connect(ui.btnCancel, SIGNAL(clicked()), this, SLOT(doCancel()));
+ ui.setupUi(this);
+
+ // populate wine select
+ ui.wine_path_combo->addItem("System Wine", QVariant{"WINE"});
+ for (const char** path : wine_paths) {
+ QDir dir(QDir::homePath() + path[0]);
+ dir.setFilter(QDir::Dirs);
+ QFileInfoList list = dir.entryInfoList();
+ for (int i = 0; i < list.size(); ++i) {
+ QFileInfo fileInfo = list.at(i);
+ if (fileInfo.fileName() == "." || fileInfo.fileName() == "..") continue;
+
+ QString name = fileInfo.fileName() + " (" + path[2] + ")";
+ ui.wine_path_combo->addItem(name, QVariant{fileInfo.filePath() + path[1]});
+ }
+ }
+ ui.wine_path_combo->addItem("Custom path to Wine executable", QVariant{"CUSTOM"});
+
+ // populate proton select
+ for (const char** path : proton_paths) {
+ QDir dir(QDir::homePath() + path[0]);
+ dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
+ dir.setNameFilters({ path[1] });
+
+ QFileInfoList proton_dir_list = dir.entryInfoList();
+ for (int i = 0; i < proton_dir_list.size(); ++i) {
+ const QFileInfo &proton_dir = proton_dir_list.at(i);
+
+ // check if this Proton Version is already present in any way
+ if (ui.proton_version->findText(proton_dir.fileName()) != -1)
+ continue;
+
+ QDirIterator proton_executable_it(proton_dir.canonicalFilePath(), QStringList() << "wine", QDir::Files, QDirIterator::Subdirectories);
+
+ if (proton_executable_it.hasNext()) {
+ QString proton_executable_path = proton_executable_it.next();
+ QDir proton_dist_dir(proton_executable_path);
+ proton_dist_dir.cd("../../");
+
+ ui.proton_version->addItem(proton_dir.fileName(), QVariant{proton_dist_dir.canonicalPath()});
+ }
+ }
+ }
+
+ // settings - wine
+ tie_setting(s.variant_wine, ui.variant_wine); // radio button
+ tie_setting(s.wine_select_path, ui.wine_path_combo); // combo box (dropdown)
+ tie_setting(s.wine_custom_path, ui.wine_path); // line edit (enabled via dropdown)
+ tie_setting(s.wineprefix, ui.wineprefix); // line edit
+
+ // settings - proton
+ tie_setting(s.variant_proton, ui.variant_proton); // radio button
+ tie_setting(s.proton_path, ui.proton_version); // combo box (dropdown)
+ tie_setting(s.variant_proton_steamplay, ui.subvariant_steamplay); // radio button
+ tie_setting(s.proton_appid, ui.proton_appid); // number select
+ tie_setting(s.variant_proton_external, ui.subvariant_external); // radio button
+ tie_setting(s.protonprefix, ui.protonprefix); // line edit
+
+ // settings - advanced
+ tie_setting(s.esync, ui.esync);
+ tie_setting(s.fsync, ui.fsync);
+ tie_setting(s.protocol, ui.protocol_selection);
+
+ // setup signals and slots for UI
+ connect(ui.wine_path_combo, &QComboBox::currentTextChanged, this, &FTControls::onWinePathComboUpdated);
+ connect(ui.browse_wine_path_button, &QPushButton::clicked, this, &FTControls::doBrowseWine);
+ connect(ui.browse_wine_prefix_button, &QPushButton::clicked, this, &FTControls::doBrowseWinePrefix);
+ connect(ui.browse_proton_prefix_button, &QPushButton::clicked, this, &FTControls::doBrowseProtonPrefix);
+ connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &FTControls::doOK);
+ connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &FTControls::doCancel);
+
+ // setup signals and slots for UI radio buttons
+ connect(ui.variant_wine, &QRadioButton::clicked, this, &FTControls::onRadioButtonsChanged);
+ connect(ui.variant_proton, &QRadioButton::clicked, this, &FTControls::onRadioButtonsChanged);
+ connect(ui.subvariant_steamplay, &QRadioButton::clicked, this, &FTControls::onRadioButtonsChanged);
+ connect(ui.subvariant_external, &QRadioButton::clicked, this, &FTControls::onRadioButtonsChanged);
+
+ // update state of the combo box and associated ui elements
+ onWinePathComboUpdated();
+ // hide the correct items
+ onRadioButtonsChanged();
}
-void FTControls::doOK() {
- close();
+void FTControls::onWinePathComboUpdated() {
+ // enable the custom text field if required
+ if (ui.wine_path_combo->currentData() == "CUSTOM") {
+ ui.wine_path->setEnabled(true);
+ ui.browse_wine_path_button->setEnabled(true);
+ }
+ else {
+ ui.wine_path->setEnabled(false);
+ ui.browse_wine_path_button->setEnabled(false);
+ }
}
-void FTControls::doCancel() {
+void FTControls::onRadioButtonsChanged() {
+ if (ui.variant_wine->isChecked()) {
+ // wine settings selected
+
+ // enable wine settings
+ ui.wine_path_combo->setEnabled(true);
+ ui.wineprefix->setEnabled(true);
+ ui.browse_wine_prefix_button->setEnabled(true);
+ if (ui.wine_path_combo->currentData() == "CUSTOM") {
+ ui.wine_path->setEnabled(true);
+ ui.browse_wine_path_button->setEnabled(true);
+ }
+
+ // disable proton settings
+ ui.proton_version->setEnabled(false);
+ ui.proton_subgroup->setEnabled(false);
+ }
+ else if (ui.variant_proton->isChecked()) {
+ // proton settings selected
+
+ // disable wine settings
+ ui.wine_path_combo->setEnabled(false);
+ ui.wine_path->setEnabled(false);
+ ui.browse_wine_path_button->setEnabled(false);
+ ui.wineprefix->setEnabled(false);
+ ui.browse_wine_prefix_button->setEnabled(false);
+
+ // enable proton settings
+ ui.proton_version->setEnabled(true);
+ ui.proton_subgroup->setEnabled(true);
+
+ // run proton radio buttons logic
+ if (ui.subvariant_steamplay->isChecked()) {
+ // enable steamplay settings
+ ui.proton_appid->setEnabled(true);
+
+ // disable external settings
+ ui.protonprefix->setEnabled(false);
+ ui.browse_proton_prefix_button->setEnabled(false);
+ }
+ else if (ui.subvariant_external->isChecked()) {
+ // disable steamplay settings
+ ui.proton_appid->setEnabled(false);
+
+ // enable external settinsg
+ ui.protonprefix->setEnabled(true);
+ ui.browse_proton_prefix_button->setEnabled(true);
+ }
+ }
+ else {
+ // for some reason QTs auto exclusive feature is not always correctly working
+ // this is a somewhat hacky solution
+ ui.variant_wine->setChecked(ui.wine_path_combo->isEnabled());
+ ui.variant_proton->setChecked(ui.proton_version->isEnabled());
+ }
+}
+
+void FTControls::doBrowseWine() {
+ QFileDialog d(this);
+ d.setFileMode(QFileDialog::FileMode::ExistingFile);
+ d.setWindowTitle(tr("Select path to Wine Binary"));
+ if (s.wine_custom_path->startsWith("~/.local/share/lutris/runners")) {
+ d.selectFile(s.wine_custom_path);
+ }
+ if (d.exec()) {
+ s.wine_custom_path = d.selectedFiles()[0];
+ }
+}
+void FTControls::doBrowseWinePrefix() {
+ QFileDialog d(this);
+ d.setFileMode(QFileDialog::FileMode::Directory);
+ d.setOption(QFileDialog::Option::ShowDirsOnly, true);
+ d.setWindowTitle(tr("Select Wine Prefix"));
+ if (s.wineprefix->startsWith("/") || s.wineprefix->startsWith("~")) {
+ d.selectFile(s.wineprefix);
+ }
+ if (d.exec()) {
+ s.wineprefix = d.selectedFiles()[0];
+ }
+}
+
+void FTControls::doBrowseProtonPrefix() {
+ QFileDialog d(this);
+ d.setFileMode(QFileDialog::FileMode::Directory);
+ d.setOption(QFileDialog::Option::ShowDirsOnly, true);
+ d.setWindowTitle(tr("Select Proton Prefix"));
+ if (s.protonprefix->startsWith("/") || s.protonprefix->startsWith("~")) {
+ d.selectFile(s.protonprefix);
+ }
+ if (d.exec()) {
+ s.protonprefix = d.selectedFiles()[0];
+ }
+}
+
+void FTControls::doOK()
+{
+ s.b->save();
close();
}
+void FTControls::doCancel()
+{
+ s.b->reload();
+ close();
+}
diff --git a/proto-wine/ftnoir_winecontrols.ui b/proto-wine/ftnoir_winecontrols.ui
index 9356c448..b601d6e5 100644
--- a/proto-wine/ftnoir_winecontrols.ui
+++ b/proto-wine/ftnoir_winecontrols.ui
@@ -9,16 +9,16 @@
<rect>
<x>0</x>
<y>0</y>
- <width>409</width>
- <height>110</height>
+ <width>934</width>
+ <height>466</height>
</rect>
</property>
<property name="windowTitle">
- <string>FreeTrack settings FaceTrackNoIR</string>
+ <string>Wine settings</string>
</property>
<property name="windowIcon">
- <iconset>
- <normaloff>images/freetrack.png</normaloff>images/freetrack.png</iconset>
+ <iconset resource="wine-protocol.qrc">
+ <normaloff>:/images/wine.png</normaloff>:/images/wine.png</iconset>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
@@ -26,141 +26,388 @@
<property name="autoFillBackground">
<bool>false</bool>
</property>
- <layout class="QVBoxLayout" name="_vertical_layout">
+ <layout class="QVBoxLayout" name="verticalLayout">
<item>
- <layout class="QHBoxLayout">
- <item>
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Wine variant</string>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLineEdit" name="wine_path">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>450</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;wine/runner exectuable path&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="inputMask">
+ <string/>
+ </property>
+ <property name="placeholderText">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="browse_wine_path_button">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Browse Wine Path</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="1">
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLineEdit" name="wineprefix">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>450</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;prefix&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="placeholderText">
+ <string>/path_to_the_prefix/</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="browse_wine_prefix_button">
+ <property name="text">
+ <string>Browse Prefix</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="1">
+ <widget class="QComboBox" name="proton_version">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>120</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QRadioButton" name="variant_proton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Proton (select version and mode)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QRadioButton" name="variant_wine">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Wine (select version and prefix)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="wine_path_combo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" colspan="2">
+ <widget class="QGroupBox" name="proton_subgroup">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="proton_appid">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>2147483647</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QRadioButton" name="subvariant_steamplay">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Steam Play is Steams System to run Windows titles on Linux via Proton.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Steam Play (select Steam Application ID)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QRadioButton" name="subvariant_external">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;UMU is a launcher that allows the use of Proton outside of Steam. Some game launchers like Lutris may use this to enable Proton support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>UMU enabled Launchers (select prefix)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <item>
+ <widget class="QLineEdit" name="protonprefix">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>450</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="browse_proton_prefix_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>105</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Browse Prefix</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
<item>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>There are no settings necessary for the Wine protocol.</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <property name="sizeConstraint">
- <enum>QLayout::SetDefaultConstraint</enum>
- </property>
- <item>
- <widget class="QPushButton" name="btnOK">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>100</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string>OK</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="btnCancel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Advanced</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="esync">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When supported.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>ESYNC</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="fsync">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When supported.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>FSYNC</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget_2" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="topMargin">
+ <number>0</number>
</property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- <property name="maximumSize">
- <size>
- <width>100</width>
- <height>16777215</height>
- </size>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Protocol</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="protocol_selection">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>Freetrack</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>NPClient</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Both</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="topMargin">
+ <number>0</number>
</property>
- <property name="text">
- <string>Cancel</string>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>10</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
</item>
</layout>
</widget>
- <resources/>
+ <tabstops>
+ <tabstop>variant_wine</tabstop>
+ <tabstop>wine_path_combo</tabstop>
+ <tabstop>wine_path</tabstop>
+ <tabstop>browse_wine_path_button</tabstop>
+ <tabstop>wineprefix</tabstop>
+ <tabstop>browse_wine_prefix_button</tabstop>
+ <tabstop>variant_proton</tabstop>
+ <tabstop>proton_version</tabstop>
+ <tabstop>subvariant_steamplay</tabstop>
+ <tabstop>proton_appid</tabstop>
+ <tabstop>subvariant_external</tabstop>
+ <tabstop>protonprefix</tabstop>
+ <tabstop>browse_proton_prefix_button</tabstop>
+ <tabstop>esync</tabstop>
+ <tabstop>fsync</tabstop>
+ <tabstop>protocol_selection</tabstop>
+ </tabstops>
+ <resources>
+ <include location="wine-protocol.qrc"/>
+ </resources>
<connections/>
<slots>
<slot>startEngineClicked()</slot>
diff --git a/proto-wine/lang/de_DE.ts b/proto-wine/lang/de_DE.ts
new file mode 100644
index 00000000..072c8626
--- /dev/null
+++ b/proto-wine/lang/de_DE.ts
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>FTControls</name>
+ <message>
+ <source>Select path to Wine Binary</source>
+ <translation>Pfad des wine-Programms auswählen</translation>
+ </message>
+ <message>
+ <source>Select Wine Prefix</source>
+ <translation>wine-Prefix auswählen</translation>
+ </message>
+ <message>
+ <source>Select Proton Prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UICFTControls</name>
+ <message>
+ <source>Wine settings</source>
+ <translation>Wine-Einstellungen</translation>
+ </message>
+ <message>
+ <source>Wine variant</source>
+ <translation>Wine-Variante</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;prefix&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Prefix&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>/path_to_the_prefix/</source>
+ <translation>/pfad_zum_prefix/</translation>
+ </message>
+ <message>
+ <source>Browse Prefix</source>
+ <translation>Prefix suchen</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;wine/runner exectuable path&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pfad zum ausführbaren wine/runner&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>Browse Wine Path</source>
+ <translation>Wine-Pfad suchen</translation>
+ </message>
+ <message>
+ <source>Advanced</source>
+ <translation>Erweitert</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When supported.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Falls unterstützt.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>ESYNC</source>
+ <translation>ESYNC</translation>
+ </message>
+ <message>
+ <source>FSYNC</source>
+ <translation>FSYNC</translation>
+ </message>
+ <message>
+ <source>Protocol</source>
+ <translation>Protokoll</translation>
+ </message>
+ <message>
+ <source>Freetrack</source>
+ <translation>Freetrack</translation>
+ </message>
+ <message>
+ <source>NPClient</source>
+ <translation>NPClient</translation>
+ </message>
+ <message>
+ <source>Both</source>
+ <translation>Beides</translation>
+ </message>
+ <message>
+ <source>Proton (select version and mode)</source>
+ <translation>Proton (wähle Version und Modus)</translation>
+ </message>
+ <message>
+ <source>Steam Play (select Steam Application ID)</source>
+ <translation>Steam Play (wähle Steam Applikation ID)</translation>
+ </message>
+ <message>
+ <source>UMU enabled Launchers (select prefix)</source>
+ <translation>UMU unterstützte Launcher (wähle Prefix)</translation>
+ </message>
+ <message>
+ <source>Wine (select version and prefix)</source>
+ <translation>Wine (wähle Version und Prefix)</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Steam Play is Steams System to run Windows titles on Linux via Proton.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Steam Play ist Steams System, um Windows Titel auf Linux, via Proton, auszuführen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;UMU is a launcher that allows the use of Proton outside of Steam. Some game launchers like Lutris may use this to enable Proton support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;UMU ist ein Launcher, der es erlaubt Proton außerhalb von Steam zu nutzen. Manche Spiel Launcher, wie Lutris, können dies für Proton Support nutzen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>wine</name>
+ <message>
+ <source>Must specify application id for Proton (Steam Play)</source>
+ <translation>Die Application-ID für Proton (Steam Play) muss angegeben werden</translation>
+ </message>
+ <message>
+ <source>Wine prefix must be an absolute path (given &apos;%1&apos;)</source>
+ <translation>Wine-Prefix muss ein absoluter Pfad sein (&apos;%1&apos; angegeben)</translation>
+ </message>
+ <message>
+ <source>Failed to start Wine! Make sure the binary is set correctly.</source>
+ <translation>Starten von Wine fehlgeschlagen! Stelle sicher, dass der Programmpfad richtig gesetzt ist.</translation>
+ </message>
+ <message>
+ <source>Can&apos;t open shared memory mapping</source>
+ <translation>Shared-Memory-Mapping kann nicht geöffnet werden</translation>
+ </message>
+ <message>
+ <source>Prefix has not been defined!</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>wine_metadata</name>
+ <message>
+ <source>Wine -- Windows layer for Unix</source>
+ <translation>Wine -- Windows-Schicht für Unix</translation>
+ </message>
+ <message>
+ <source>X-Plane</source>
+ <translation>X-Plane</translation>
+ </message>
+</context>
+</TS>
diff --git a/proto-wine/lang/nl_NL.ts b/proto-wine/lang/nl_NL.ts
index 4afcafa8..bc3ef604 100644
--- a/proto-wine/lang/nl_NL.ts
+++ b/proto-wine/lang/nl_NL.ts
@@ -2,25 +2,138 @@
<!DOCTYPE TS>
<TS version="2.1" language="nl_NL">
<context>
+ <name>FTControls</name>
+ <message>
+ <source>Select path to Wine Binary</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Wine Prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Proton Prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>UICFTControls</name>
<message>
- <location filename="../ftnoir_winecontrols.ui" line="+17"/>
- <source>FreeTrack settings FaceTrackNoIR</source>
+ <source>Wine settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wine variant</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Advanced</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ESYNC</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FSYNC</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When supported.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Protocol</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Freetrack</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>NPClient</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Both</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;prefix&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>/path_to_the_prefix/</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+48"/>
- <source>There are no settings necessary for the Wine protocol.</source>
+ <source>Browse Prefix</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+47"/>
- <source>OK</source>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;wine/runner exectuable path&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Browse Wine Path</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Proton (select version and mode)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Steam Play (select Steam Application ID)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>UMU enabled Launchers (select prefix)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wine (select version and prefix)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Steam Play is Steams System to run Windows titles on Linux via Proton.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;UMU is a launcher that allows the use of Proton outside of Steam. Some game launchers like Lutris may use this to enable Proton support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>wine</name>
+ <message>
+ <source>Can&apos;t open shared memory mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Must specify application id for Proton (Steam Play)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wine prefix must be an absolute path (given &apos;%1&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Failed to start Wine! Make sure the binary is set correctly.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Prefix has not been defined!</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>wine_metadata</name>
+ <message>
+ <source>Wine -- Windows layer for Unix</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+25"/>
- <source>Cancel</source>
+ <source>X-Plane</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-wine/lang/ru_RU.ts b/proto-wine/lang/ru_RU.ts
index 1fc4ce25..a7702454 100644
--- a/proto-wine/lang/ru_RU.ts
+++ b/proto-wine/lang/ru_RU.ts
@@ -2,25 +2,138 @@
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
<context>
+ <name>FTControls</name>
+ <message>
+ <source>Select path to Wine Binary</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Wine Prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Proton Prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>UICFTControls</name>
<message>
- <location filename="../ftnoir_winecontrols.ui" line="+17"/>
- <source>FreeTrack settings FaceTrackNoIR</source>
+ <source>Wine settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wine variant</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Advanced</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ESYNC</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FSYNC</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When supported.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Protocol</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Freetrack</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>NPClient</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Both</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;prefix&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>/path_to_the_prefix/</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+48"/>
- <source>There are no settings necessary for the Wine protocol.</source>
+ <source>Browse Prefix</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+47"/>
- <source>OK</source>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;wine/runner exectuable path&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Browse Wine Path</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Proton (select version and mode)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Steam Play (select Steam Application ID)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>UMU enabled Launchers (select prefix)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wine (select version and prefix)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Steam Play is Steams System to run Windows titles on Linux via Proton.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;UMU is a launcher that allows the use of Proton outside of Steam. Some game launchers like Lutris may use this to enable Proton support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>wine</name>
+ <message>
+ <source>Can&apos;t open shared memory mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Must specify application id for Proton (Steam Play)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wine prefix must be an absolute path (given &apos;%1&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Failed to start Wine! Make sure the binary is set correctly.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Prefix has not been defined!</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>wine_metadata</name>
+ <message>
+ <source>Wine -- Windows layer for Unix</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+25"/>
- <source>Cancel</source>
+ <source>X-Plane</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-wine/lang/stub.ts b/proto-wine/lang/stub.ts
index bcd7fb82..2c708749 100644
--- a/proto-wine/lang/stub.ts
+++ b/proto-wine/lang/stub.ts
@@ -2,25 +2,138 @@
<!DOCTYPE TS>
<TS version="2.1">
<context>
+ <name>FTControls</name>
+ <message>
+ <source>Select path to Wine Binary</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Wine Prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Proton Prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>UICFTControls</name>
<message>
- <location filename="../ftnoir_winecontrols.ui" line="+17"/>
- <source>FreeTrack settings FaceTrackNoIR</source>
+ <source>Wine settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wine variant</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Advanced</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ESYNC</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FSYNC</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When supported.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Protocol</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Freetrack</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>NPClient</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Both</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;prefix&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>/path_to_the_prefix/</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+48"/>
- <source>There are no settings necessary for the Wine protocol.</source>
+ <source>Browse Prefix</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+47"/>
- <source>OK</source>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;wine/runner exectuable path&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Browse Wine Path</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Proton (select version and mode)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Steam Play (select Steam Application ID)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>UMU enabled Launchers (select prefix)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wine (select version and prefix)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Steam Play is Steams System to run Windows titles on Linux via Proton.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;UMU is a launcher that allows the use of Proton outside of Steam. Some game launchers like Lutris may use this to enable Proton support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>wine</name>
+ <message>
+ <source>Can&apos;t open shared memory mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Must specify application id for Proton (Steam Play)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wine prefix must be an absolute path (given &apos;%1&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Failed to start Wine! Make sure the binary is set correctly.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Prefix has not been defined!</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>wine_metadata</name>
+ <message>
+ <source>Wine -- Windows layer for Unix</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+25"/>
- <source>Cancel</source>
+ <source>X-Plane</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/proto-wine/lang/zh_CN.ts b/proto-wine/lang/zh_CN.ts
new file mode 100644
index 00000000..6884f266
--- /dev/null
+++ b/proto-wine/lang/zh_CN.ts
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>FTControls</name>
+ <message>
+ <source>Select path to Wine Binary</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Wine Prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Proton Prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UICFTControls</name>
+ <message>
+ <source>Wine settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wine variant</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Advanced</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ESYNC</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FSYNC</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When supported.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Protocol</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Freetrack</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>NPClient</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Both</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;prefix&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>/path_to_the_prefix/</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Browse Prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;wine/runner exectuable path&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Browse Wine Path</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Proton (select version and mode)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Steam Play (select Steam Application ID)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>UMU enabled Launchers (select prefix)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wine (select version and prefix)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Steam Play is Steams System to run Windows titles on Linux via Proton.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;UMU is a launcher that allows the use of Proton outside of Steam. Some game launchers like Lutris may use this to enable Proton support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>wine</name>
+ <message>
+ <source>Can&apos;t open shared memory mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Must specify application id for Proton (Steam Play)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Wine prefix must be an absolute path (given &apos;%1&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Failed to start Wine! Make sure the binary is set correctly.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Prefix has not been defined!</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>wine_metadata</name>
+ <message>
+ <source>Wine -- Windows layer for Unix</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>X-Plane</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/proto-wine/opentrack-wrapper-wine-main.cxx b/proto-wine/opentrack-wrapper-wine-main.cxx
index 1c88755a..9248a212 100644
--- a/proto-wine/opentrack-wrapper-wine-main.cxx
+++ b/proto-wine/opentrack-wrapper-wine-main.cxx
@@ -1,18 +1,19 @@
#include <cerrno>
+
// OSX sdk 10.8 build error otherwise
-#ifdef _LIBCPP_MSVCRT
-# undef _LIBCPP_MSVCRT
-#endif
+#undef _LIBCPP_MSVCRT
#include <cstdio>
+
#include "freetrackclient/fttypes.h"
-#include "wine-shm.h"
#include "compat/export.hpp"
enum Axis {
TX = 0, TY, TZ, Yaw, Pitch, Roll
};
+#define __WINE_OLE2_H
#include "compat/shm.h"
+#include "wine-shm.h"
void create_registry_key(void);
@@ -41,18 +42,17 @@ private:
void* mem;
void *hMutex, *hMapFile;
};
-#include <windows.h>
int main(void)
{
ShmPosix lck_posix(WINE_SHM_NAME, WINE_MTX_NAME, sizeof(WineSHM));
ShmWine lck_wine("FT_SharedMem", "FT_Mutext", sizeof(FTHeap));
if(!lck_posix.success()) {
- printf("Can't open posix map: %d\n", errno);
+ fprintf(stderr, "Can't open posix map: %d\n", errno);
return 1;
}
if(!lck_wine.success()) {
- printf("Can't open Wine map\n");
+ fprintf(stderr, "Can't open Wine map\n");
return 1;
}
WineSHM* shm_posix = (WineSHM*) lck_posix.ptr();
diff --git a/proto-wine/opentrack-wrapper-wine-windows.cxx b/proto-wine/opentrack-wrapper-wine-windows.cxx
index e1f91797..c1d552e1 100644
--- a/proto-wine/opentrack-wrapper-wine-windows.cxx
+++ b/proto-wine/opentrack-wrapper-wine-windows.cxx
@@ -3,19 +3,23 @@
#endif
#define shm_wrapper ShmWine
+#define __WINE_OLE2_H
+// OSX sdk 10.8 build error otherwise
+#undef _LIBCPP_MSVCRT
+
#include "compat/shm.h"
#include "compat/shm.cpp"
#include "wine-shm.h"
-#define OPENTRACK_NO_QT_PATH
#include "compat/library-path.hpp"
+#include <cstdlib>
#include <cstring>
+#include <sysexits.h>
using std::strcat;
-static void write_path(const char* key, const char* subkey)
+static void write_path(const char* key, const char* subkey, bool path)
{
- char dir[8192];
- dir[sizeof(dir)-1] = '\0';
+ char dir[8192] {};
if (GetCurrentDirectoryA(8192, dir) < 8190)
{
@@ -36,13 +40,32 @@ static void write_path(const char* key, const char* subkey)
// there's always a leading and trailing slash
strcat(dir, OPENTRACK_LIBRARY_PATH);
//strcat(dir, "/");
+ if (!path)
+ dir[0] = '\0';
(void) RegSetValueExA(hkpath, subkey, 0, REG_SZ, (BYTE*) dir, strlen(dir) + 1);
RegCloseKey(hkpath);
}
}
}
-void create_registry_key(void) {
- write_path("Software\\NaturalPoint\\NATURALPOINT\\NPClient Location", "Path");
- write_path("Software\\Freetrack\\FreeTrackClient", "Path");
+void create_registry_key(void)
+{
+ bool use_freetrack, use_npclient;
+ const char* env = getenv("OTR_WINE_PROTO");
+ char* endptr;
+ if (!env) env = "";
+ int selection = strtol(env, &endptr, 10);
+ if (*endptr)
+ selection = 0;
+
+ switch (selection)
+ {
+ default: std::exit(EX_USAGE);
+ case 1: use_freetrack = true, use_npclient = false; break;
+ case 2: use_freetrack = false, use_npclient = true; break;
+ case 3: use_freetrack = true, use_npclient = true; break;
+ }
+
+ write_path("Software\\NaturalPoint\\NATURALPOINT\\NPClient Location", "Path", use_npclient);
+ write_path("Software\\Freetrack\\FreeTrackClient", "Path", use_freetrack);
}
diff --git a/proto-wine/proton.cpp b/proto-wine/proton.cpp
new file mode 100644
index 00000000..998da748
--- /dev/null
+++ b/proto-wine/proton.cpp
@@ -0,0 +1,97 @@
+/* Copyright (c) 2019 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * 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.
+ */
+
+#ifndef OTR_WINE_NO_WRAPPER
+
+#include <QDebug>
+#include <QDir>
+#include <QFileInfo>
+#include <QtGlobal>
+
+#include "proton.h"
+
+static const char* steam_paths[] = {
+ "/.steam/steam/steamapps/compatdata",
+ "/.local/share/Steam/steamapps/compatdata",
+ "/.steam/debian-installation/steamapps/compatdata",
+};
+
+static const char* runtime_paths[] = {
+ "/.local/share/Steam/ubuntu12_32/steam-runtime",
+ "/.steam/ubuntu12_32/steam-runtime",
+ "/.steam/debian-installation/ubuntu12_32/steam-runtime",
+};
+
+
+std::tuple<QProcessEnvironment, QString, bool> make_steam_environ(const QString& proton_dist_path)
+{
+ using ret = std::tuple<QProcessEnvironment, QString, bool>;
+ auto env = QProcessEnvironment::systemEnvironment();
+ QString error = "";
+ QString home = qgetenv("HOME");
+ QString runtime_path;
+
+ auto expand = [&](QString x) {
+ x.replace("HOME", home);
+ x.replace("PROTON_DIST_PATH", proton_dist_path);
+ x.replace("RUNTIME_PATH", runtime_path);
+ return x;
+ };
+
+ for (const char* path : runtime_paths) {
+ QDir dir(QDir::homePath() + path);
+ if (dir.exists())
+ runtime_path = dir.absolutePath();
+ }
+
+ if (runtime_path.isEmpty())
+ error = QString("Couldn't find a Steam runtime.");
+
+ QString path = expand(
+ ":PROTON_DIST_PATH/bin"
+ );
+ path += ':'; path += qgetenv("PATH");
+ env.insert("PATH", path);
+
+ QString library_path = expand(
+ ":PROTON_DIST_PATH/lib"
+ ":PROTON_DIST_PATH/lib64"
+ ":RUNTIME_PATH/pinned_libs_32"
+ ":RUNTIME_PATH/pinned_libs_64"
+ ":RUNTIME_PATH/i386/lib/i386-linux-gnu"
+ ":RUNTIME_PATH/i386/lib"
+ ":RUNTIME_PATH/i386/usr/lib/i386-linux-gnu"
+ ":RUNTIME_PATH/i386/usr/lib"
+ ":RUNTIME_PATH/amd64/lib/x86_64-linux-gnu"
+ ":RUNTIME_PATH/amd64/lib"
+ ":RUNTIME_PATH/amd64/usr/lib/x86_64-linux-gnu"
+ ":RUNTIME_PATH/amd64/usr/lib"
+ );
+ library_path += ':'; library_path += qgetenv("LD_LIBRARY_PATH");
+ env.insert("LD_LIBRARY_PATH", library_path);
+
+ return ret(env, error, error.isEmpty());
+}
+
+
+std::tuple<QString, QString, bool> make_wineprefix(int appid)
+{
+ using ret = std::tuple<QString, QString, bool>;
+ QString error = "";
+ QString app_wineprefix;
+ for (const char* path : steam_paths) {
+ QDir dir(QDir::homePath() + path + QString("/%1/pfx").arg(appid));
+ if (dir.exists())
+ app_wineprefix = dir.absolutePath();
+ }
+ if (app_wineprefix.isEmpty())
+ error = QString("Couldn't find a Wineprefix for AppId %1").arg(appid);
+
+ return ret(app_wineprefix, error, error.isEmpty());
+}
+
+#endif
diff --git a/proto-wine/proton.h b/proto-wine/proton.h
new file mode 100644
index 00000000..51539305
--- /dev/null
+++ b/proto-wine/proton.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <qchar.h>
+#include <qprocess.h>
+
+std::tuple<QProcessEnvironment, QString, bool> make_steam_environ(const QString& proton_dist_path);
+std::tuple<QString, QString, bool> make_wineprefix(int appid); \ No newline at end of file
diff --git a/proto-wine/wine-shm.h b/proto-wine/wine-shm.h
index a717dcc8..62e8bbec 100644
--- a/proto-wine/wine-shm.h
+++ b/proto-wine/wine-shm.h
@@ -3,12 +3,14 @@
#define WINE_SHM_NAME "facetracknoir-wine-shm"
#define WINE_MTX_NAME "facetracknoir-wine-mtx"
-// OSX sdk 10.8 build error otherwise
-#undef _LIBCPP_MSVCRT
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wreserved-id-macro"
+#endif
-#include <memory>
-
-template<typename t> using ptr = std::shared_ptr<t>;
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
struct WineSHM {
double data[6];
diff --git a/qxt-mini/CMakeLists.txt b/qxt-mini/CMakeLists.txt
index 39f3525c..1b2496f6 100644
--- a/qxt-mini/CMakeLists.txt
+++ b/qxt-mini/CMakeLists.txt
@@ -1,13 +1,13 @@
if(UNIX OR APPLE)
+ set(self opentrack-qxt-mini)
+ include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
otr_module(qxt-mini NO-COMPAT BIN)
- if(NOT APPLE)
- pkg_check_modules(xprotopkg REQUIRED xproto)
- target_include_directories(opentrack-qxt-mini SYSTEM PRIVATE ${xprotopkg_INCLUDE_DIRS})
- target_link_libraries(opentrack-qxt-mini X11)
- add_definitions(-DQXT_BUILD)
+ if(APPLE)
+ find_library(CoreFoundation CoreFoundation)
+ find_library(Carbon Carbon)
+ target_link_options(${self} PUBLIC -framework Carbon -I ${CoreFoundation})
else()
- set_property(TARGET opentrack-qxt-mini
- APPEND_STRING PROPERTY
- LINK_FLAGS "-framework Carbon -framework CoreFoundation ")
+ otr_pkgconfig(${self} x11 xcb xproto)
endif()
+ target_compile_definitions(${self} PRIVATE -DQXT_BUILD)
endif()
diff --git a/qxt-mini/lang/de_DE.ts b/qxt-mini/lang/de_DE.ts
new file mode 100644
index 00000000..1552582e
--- /dev/null
+++ b/qxt-mini/lang/de_DE.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+</TS>
diff --git a/qxt-mini/lang/zh_CN.ts b/qxt-mini/lang/zh_CN.ts
new file mode 100644
index 00000000..e5ca8aa9
--- /dev/null
+++ b/qxt-mini/lang/zh_CN.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+</TS>
diff --git a/qxt-mini/powerset.hpp b/qxt-mini/powerset.hpp
index ebb02ba8..901ff0c7 100644
--- a/qxt-mini/powerset.hpp
+++ b/qxt-mini/powerset.hpp
@@ -1,7 +1,5 @@
#pragma once
-#include "compat/macros.hpp"
-
#include <type_traits>
#include <cinttypes>
#include <vector>
@@ -66,8 +64,7 @@ private:
};
template<typename t, typename... xs>
-auto force_inline
-make_powerset(const t& arg, const xs&... args)
+inline auto make_powerset(const t& arg, const xs&... args)
{
using cnt = std::integral_constant<std::uintptr_t, sizeof...(xs)+1>;
using p = powerset<t, cnt::value>;
diff --git a/qxt-mini/qplatformnativeinterface.h b/qxt-mini/qplatformnativeinterface.h
deleted file mode 100644
index eaa24a9e..00000000
--- a/qxt-mini/qplatformnativeinterface.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
-** Contact: http://www.qt-project.org/legal
-**
-** This file is part of the QtGui module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and Digia. For licensing terms and
-** conditions see http://qt.digia.com/licensing. For further information
-** use the contact form at http://qt.digia.com/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 2.1 requirements
-** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
-**
-** In addition, as a special exception, Digia gives you certain additional
-** rights. These rights are described in the Digia Qt LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3.0 as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU General Public License version 3.0 requirements will be
-** met: http://www.gnu.org/copyleft/gpl.html.
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifndef QPLATFORMNATIVEINTERFACE_H
-#define QPLATFORMNATIVEINTERFACE_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is part of the QPA API and is not meant to be used
-// in applications. Usage of this API may make your code
-// source and binary incompatible with future versions of Qt.
-//
-
-#include <QtGui/qwindowdefs.h>
-#include <QtCore/QObject>
-#include <QtCore/QVariant>
-
-QT_BEGIN_NAMESPACE
-
-
-class QOpenGLContext;
-class QScreen;
-class QWindow;
-class QPlatformWindow;
-class QBackingStore;
-
-class Q_GUI_EXPORT QPlatformNativeInterface : public QObject
-{
- Q_OBJECT
-public:
- virtual void *nativeResourceForIntegration(const QByteArray &resource);
- virtual void *nativeResourceForContext(const QByteArray &resource, QOpenGLContext *context);
- virtual void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen);
- virtual void *nativeResourceForWindow(const QByteArray &resource, QWindow *window);
- virtual void *nativeResourceForBackingStore(const QByteArray &resource, QBackingStore *backingStore);
-
- typedef void * (*NativeResourceForIntegrationFunction)();
- typedef void * (*NativeResourceForContextFunction)(QOpenGLContext *context);
- typedef void * (*NativeResourceForScreenFunction)(QScreen *screen);
- typedef void * (*NativeResourceForWindowFunction)(QWindow *window);
- typedef void * (*NativeResourceForBackingStoreFunction)(QBackingStore *backingStore);
- virtual NativeResourceForIntegrationFunction nativeResourceFunctionForIntegration(const QByteArray &resource);
- virtual NativeResourceForContextFunction nativeResourceFunctionForContext(const QByteArray &resource);
- virtual NativeResourceForScreenFunction nativeResourceFunctionForScreen(const QByteArray &resource);
- virtual NativeResourceForWindowFunction nativeResourceFunctionForWindow(const QByteArray &resource);
- virtual NativeResourceForBackingStoreFunction nativeResourceFunctionForBackingStore(const QByteArray &resource);
-
- virtual QVariantMap windowProperties(QPlatformWindow *window) const;
- virtual QVariant windowProperty(QPlatformWindow *window, const QString &name) const;
- virtual QVariant windowProperty(QPlatformWindow *window, const QString &name, const QVariant &defaultValue) const;
- virtual void setWindowProperty(QPlatformWindow *window, const QString &name, const QVariant &value);
-
-Q_SIGNALS:
- void windowPropertyChanged(QPlatformWindow *window, const QString &propertyName);
-};
-
-QT_END_NAMESPACE
-
-#endif // QPLATFORMNATIVEINTERFACE_H
diff --git a/qxt-mini/qxtglobalshortcut.cpp b/qxt-mini/qxtglobalshortcut.cpp
index b680c5f7..dec11dc4 100644
--- a/qxt-mini/qxtglobalshortcut.cpp
+++ b/qxt-mini/qxtglobalshortcut.cpp
@@ -36,12 +36,11 @@
#include <QtDebug>
#include <QtGlobal>
-QHash<QPair<quint32, quint32>, QxtGlobalShortcut*> QxtGlobalShortcutPrivate::shortcuts;
+#ifdef __GNUG__
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
-struct QxtGlobalShortcutPrivate::event_filter_installer
-{
- static void ensure_event_filter();
-};
+QHash<QPair<quint32, quint32>, QxtGlobalShortcut*> QxtGlobalShortcutPrivate::shortcuts;
void QxtGlobalShortcutPrivate::event_filter_installer::ensure_event_filter()
{
@@ -51,9 +50,8 @@ void QxtGlobalShortcutPrivate::event_filter_installer::ensure_event_filter()
{
static QxtGlobalShortcutPrivate filter(QxtGlobalShortcutPrivate::tag {});
static bool installed =
- (instance->installNativeEventFilter(&filter),
- true);
- Q_UNUSED(installed);
+ ((void)instance->installNativeEventFilter(&filter), true);
+ Q_UNUSED(installed)
}
#endif
}
@@ -61,7 +59,7 @@ void QxtGlobalShortcutPrivate::event_filter_installer::ensure_event_filter()
QxtGlobalShortcutPrivate::QxtGlobalShortcutPrivate(QxtGlobalShortcutPrivate::tag) :
keystate(false), enabled(false), key(Qt::Key(0)), mods(Qt::NoModifier)
{
- qDebug() << "qxt-mini: adding event filter";
+ //qDebug() << "qxt-mini: adding event filter";
}
QxtGlobalShortcutPrivate::QxtGlobalShortcutPrivate() :
@@ -81,8 +79,8 @@ bool QxtGlobalShortcutPrivate::setShortcut(const QKeySequence& shortcut)
if (shortcut.toString() == "") return false;
Qt::KeyboardModifiers allMods = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier;
- key = shortcut.isEmpty() ? Qt::Key(0) : Qt::Key(shortcut[0] & ~allMods);
- mods = shortcut.isEmpty() ? Qt::KeyboardModifiers(0) : Qt::KeyboardModifiers(shortcut[0] & allMods);
+ key = shortcut.isEmpty() ? Qt::Key(0) : Qt::Key((unsigned)shortcut[0] & ~allMods);
+ mods = shortcut.isEmpty() ? Qt::KeyboardModifier(0) : Qt::KeyboardModifiers((unsigned)shortcut[0] & allMods);
const quint32 nativeKey = nativeKeycode(key);
const quint32 nativeMods = nativeModifiers(mods);
@@ -96,7 +94,7 @@ bool QxtGlobalShortcutPrivate::setShortcut(const QKeySequence& shortcut)
#endif
}
else
- qWarning() << "QxtGlobalShortcut failed to register:" << QKeySequence(key + mods).toString();
+ qWarning() << "QxtGlobalShortcut failed to register:" << shortcut;
return res;
}
@@ -105,10 +103,10 @@ bool QxtGlobalShortcutPrivate::unsetShortcut()
if (key == Qt::Key(0))
return true;
- bool res = false;
const quint32 nativeKey = nativeKeycode(key);
const quint32 nativeMods = nativeModifiers(mods);
#ifdef Q_OS_MAC
+ bool res = false;
if (shortcuts.value(qMakePair(nativeKey, nativeMods)) == &qxt_p())
res = unregisterShortcut(nativeKey, nativeMods);
if (res)
@@ -134,10 +132,10 @@ bool QxtGlobalShortcutPrivate::unsetShortcut()
}
if (!found)
qDebug() << "qxt-mini: can't find shortcut for" << key << mods;
- res = unregisterShortcut(nativeKey, nativeMods);
+ bool res = unregisterShortcut(nativeKey, nativeMods);
#endif
key = Qt::Key(0);
- mods = Qt::KeyboardModifiers(0);
+ mods = Qt::KeyboardModifier(0);
return res;
}
@@ -182,7 +180,7 @@ void QxtGlobalShortcutPrivate::activateShortcut(quint32 nativeKey, quint32 nativ
shortcut->qxt_d().keystate = false;
if (shortcut->isEnabled())
- emit shortcut->activated(true);
+ emit shortcut->activated(is_down);
}
#endif
}
@@ -221,7 +219,7 @@ void QxtGlobalShortcutPrivate::activateShortcut(quint32 nativeKey, quint32 nativ
QxtGlobalShortcut::QxtGlobalShortcut(QObject* parent)
: QObject(parent)
{
- QXT_INIT_PRIVATE(QxtGlobalShortcut);
+ QXT_INIT_PRIVATE(QxtGlobalShortcut)
}
/*!
@@ -230,7 +228,7 @@ QxtGlobalShortcut::QxtGlobalShortcut(QObject* parent)
QxtGlobalShortcut::QxtGlobalShortcut(const QKeySequence& shortcut, QObject* parent)
: QObject(parent)
{
- QXT_INIT_PRIVATE(QxtGlobalShortcut);
+ QXT_INIT_PRIVATE(QxtGlobalShortcut)
setShortcut(shortcut);
}
@@ -247,7 +245,7 @@ QxtGlobalShortcut::~QxtGlobalShortcut()
\property QxtGlobalShortcut::shortcut
\brief the shortcut key sequence
- \bold {Note:} Notice that corresponding key press and release events are not
+ \note Notice that corresponding key press and release events are not
delivered for registered global shortcuts even if they are disabled.
Also, comma separated key sequences are not supported.
Only the first part is used:
@@ -259,7 +257,7 @@ QxtGlobalShortcut::~QxtGlobalShortcut()
*/
QKeySequence QxtGlobalShortcut::shortcut() const
{
- return QKeySequence(qxt_d().key | qxt_d().mods);
+ return QKeySequence((int)(qxt_d().key | qxt_d().mods));
}
bool QxtGlobalShortcut::setShortcut(const QKeySequence& shortcut)
diff --git a/qxt-mini/qxtglobalshortcut.h b/qxt-mini/qxtglobalshortcut.h
index be62a984..9f8dcf9e 100644
--- a/qxt-mini/qxtglobalshortcut.h
+++ b/qxt-mini/qxtglobalshortcut.h
@@ -1,4 +1,4 @@
-#ifndef QXTGLOBALSHORTCUT_H
+#pragma once
/****************************************************************************
** Copyright (c) 2006 - 2011, the LibQxt project.
** See the Qxt AUTHORS file for a list of authors and copyright holders.
@@ -46,8 +46,8 @@ class QXT_GUI_EXPORT QxtGlobalShortcut : public QObject
bool keystate;
public:
- explicit QxtGlobalShortcut(QObject* parent = 0);
- explicit QxtGlobalShortcut(const QKeySequence& shortcut, QObject* parent = 0);
+ explicit QxtGlobalShortcut(QObject* parent = nullptr);
+ explicit QxtGlobalShortcut(const QKeySequence& shortcut, QObject* parent = nullptr);
~QxtGlobalShortcut() override;
QKeySequence shortcut() const;
@@ -62,5 +62,3 @@ public Q_SLOTS:
Q_SIGNALS:
void activated(bool keydown = true);
};
-
-#endif // QXTGLOBALSHORTCUT_H
diff --git a/qxt-mini/qxtglobalshortcut_mac.cpp b/qxt-mini/qxtglobalshortcut_mac.cpp
index d7cd7f84..c91d763d 100644
--- a/qxt-mini/qxtglobalshortcut_mac.cpp
+++ b/qxt-mini/qxtglobalshortcut_mac.cpp
@@ -49,13 +49,14 @@ OSStatus qxt_mac_handle_hot_key(EventHandlerCallRef nextHandler, EventRef event,
{
Q_UNUSED(nextHandler);
Q_UNUSED(data);
- if (GetEventClass(event) == kEventClassKeyboard && GetEventKind(event) == kEventHotKeyPressed)
+ if (GetEventClass(event) == kEventClassKeyboard && (GetEventKind(event) == kEventHotKeyPressed || GetEventKind(event) == kEventHotKeyReleased))
{
EventHotKeyID keyID;
GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(keyID), NULL, &keyID);
Identifier id = keyIDs.key(keyID.id);
- if(id != Identifier())
- QxtGlobalShortcutPrivate::activateShortcut(id.second, id.first, true);
+ if(id != Identifier()) {
+ QxtGlobalShortcutPrivate::activateShortcut(id.second, id.first, GetEventKind(event) == kEventHotKeyPressed);
+ }
}
return noErr;
}
@@ -233,10 +234,12 @@ bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativ
if (!qxt_mac_handler_installed)
{
qxt_mac_handler_installed = true;
- EventTypeSpec t;
- t.eventClass = kEventClassKeyboard;
- t.eventKind = kEventHotKeyPressed;
- InstallApplicationEventHandler(&qxt_mac_handle_hot_key, 1, &t, NULL, NULL);
+ EventTypeSpec t[2];
+ t[0].eventClass = kEventClassKeyboard;
+ t[0].eventKind = kEventHotKeyPressed;
+ t[1].eventClass = kEventClassKeyboard;
+ t[1].eventKind = kEventHotKeyReleased;
+ InstallApplicationEventHandler(&qxt_mac_handle_hot_key, 2, t, NULL, NULL);
}
EventHotKeyID keyID;
diff --git a/qxt-mini/qxtglobalshortcut_p.h b/qxt-mini/qxtglobalshortcut_p.h
index 7864a7d8..be708ba6 100644
--- a/qxt-mini/qxtglobalshortcut_p.h
+++ b/qxt-mini/qxtglobalshortcut_p.h
@@ -54,13 +54,12 @@ public:
bool unsetShortcut();
static bool error;
- static bool eventFilter(void* message);
bool nativeEventFilter(const QByteArray & eventType, void * message, long * result) override;
static void activateShortcut(quint32 nativeKey, quint32 nativeMods, bool is_down);
private:
- struct event_filter_installer;
+ struct event_filter_installer { static void ensure_event_filter(); };
friend struct event_filter_installer;
struct tag {};
explicit QxtGlobalShortcutPrivate(tag);
@@ -72,8 +71,6 @@ private:
static bool unregisterShortcut(quint32 nativeKey, quint32 nativeMods);
static QHash<QPair<quint32, quint32>, QxtGlobalShortcut*> shortcuts;
-
- static void ensure_event_filter();
};
#endif // QXTGLOBALSHORTCUT_P_H
diff --git a/qxt-mini/qxtglobalshortcut_x11.cpp b/qxt-mini/qxtglobalshortcut_x11.cpp
index 2609aa7c..01894cfc 100644
--- a/qxt-mini/qxtglobalshortcut_x11.cpp
+++ b/qxt-mini/qxtglobalshortcut_x11.cpp
@@ -37,7 +37,7 @@
#include <QPair>
#include <QKeyEvent>
#include <QApplication>
-#include "qplatformnativeinterface.h"
+#include <qpa/qplatformnativeinterface.h>
#include "x11-keymap.hpp"
@@ -69,6 +69,9 @@ using pair = QPair<quint32, quint32>;
struct keybinding final
{
+ keybinding(const keybinding&) = default;
+ keybinding& operator=(const keybinding&) = default;
+
quint32 code, mods;
int refcnt;
@@ -93,7 +96,7 @@ public:
static int qxtX11ErrorHandler(Display *display, XErrorEvent *event)
{
- Q_UNUSED(display);
+ Q_UNUSED(display)
switch (event->error_code)
{
case BadAccess:
@@ -135,7 +138,7 @@ public:
bool isValid()
{
- return m_display != 0;
+ return m_display;
}
Display *display()
@@ -154,13 +157,12 @@ public:
const std::vector<pair> keycodes = native_key(Qt::Key(code), Qt::KeyboardModifiers(mods));
bool ret = true;
- for (pair x : keycodes)
+ for (const pair& x : keycodes)
{
- int native_code = x.first, native_mods = x.second;
-
- native_code = XKeysymToKeycode(display(), native_code);
+ auto [ native_sym, native_mods ] = x;
+ int native_code = XKeysymToKeycode(display(), native_sym);
- if (keybinding::incf(native_code, native_mods))
+ if (keybinding::incf((unsigned)native_code, native_mods))
{
QxtX11ErrorHandler errorHandler;
@@ -170,8 +172,8 @@ public:
{
quint32 m = native_mods;
- for (auto value : set)
- m |= value;
+ for (int value : set)
+ m |= (unsigned)value;
XGrabKey(display(), native_code, m, rootWindow(), True, GrabModeAsync, GrabModeAsync);
}
@@ -179,7 +181,15 @@ public:
if (errorHandler.error)
{
qDebug() << "qxt-mini: error while binding to" << code << mods;
- ungrabKey(code, mods);
+ for (const auto& set : evil_mods.sets())
+ {
+ quint32 m = mods;
+
+ for (int value : set)
+ m |= (unsigned)value;
+
+ XUngrabKey(display(), native_code, m, rootWindow());
+ }
ret = false;
}
}
@@ -193,12 +203,12 @@ public:
const std::vector<pair> keycodes = native_key(Qt::Key(code), Qt::KeyboardModifiers(mods));
bool ret = true;
- for (pair x : keycodes)
+ for (const pair& x : keycodes)
{
- int native_code = x.first, native_mods = x.second;
- native_code = XKeysymToKeycode(display(), native_code);
+ auto [ native_sym, native_mods ] = x;
+ int native_code = XKeysymToKeycode(display(), native_sym);
- if (keybinding::decf(native_code, native_mods))
+ if (keybinding::decf((unsigned)native_code, native_mods))
{
QxtX11ErrorHandler errorHandler;
XUngrabKey(display(), native_code, native_mods, rootWindow());
@@ -207,10 +217,10 @@ public:
{
quint32 m = mods;
- for (auto value : set)
- m |= value;
+ for (int value : set)
+ m |= (unsigned)value;
- XUngrabKey(display(), code, m, rootWindow());
+ XUngrabKey(display(), native_code, m, rootWindow());
}
if (errorHandler.error)
@@ -345,26 +355,27 @@ bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray & eventType,
(void) XkbSetDetectableAutoRepeat(x11.display(), True, &val);
- if (val)
- qDebug() << "qxt-mini: fixed x11 autorepeat";
- else
+ if (!val)
qDebug() << "qxt-mini: can't fix x11 autorepeat";
}
}
bool is_release = false;
- xcb_key_press_event_t *kev = 0;
+ static_assert(std::is_same_v<xcb_key_press_event_t, xcb_key_release_event_t>);
+
+ xcb_key_press_event_t *kev = nullptr;
if (eventType == "xcb_generic_event_t") {
xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message);
switch (ev->response_type & 127)
{
case XCB_KEY_RELEASE:
is_release = true;
- /*FALLTHROUGH*/
+ [[fallthrough]];
case XCB_KEY_PRESS:
kev = static_cast<xcb_key_press_event_t *>(message);
- /*FALLTHROUGH*/
+ break;
+
default:
break;
}
@@ -400,21 +411,19 @@ bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray & eventType,
keystate = filter_evil_mods(keystate);
- QPair<KeySym, KeySym> sym_ = keycode_to_keysym(x11.display(), keycode, keystate, kev);
- KeySym sym = sym_.first, sym2 = sym_.second;
+ auto [ sym, sym2 ] = keycode_to_keysym(x11.display(), keycode, keystate, kev);
Qt::Key k; Qt::KeyboardModifiers mods;
-
{
- std::tie(k, mods) = x11_key_to_qt(x11.display(), sym, keystate);
+ std::tie(k, mods) = x11_key_to_qt(x11.display(), (quint32)sym, keystate);
if (k != 0)
activateShortcut(k, mods, !is_release);
}
{
- std::tie(k, mods) = x11_key_to_qt(x11.display(), sym2, keystate);
+ std::tie(k, mods) = x11_key_to_qt(x11.display(), (quint32)sym2, keystate);
if (k != 0)
activateShortcut(k, mods, !is_release);
diff --git a/qxt-mini/x11-keymap.cpp b/qxt-mini/x11-keymap.cpp
index a42752b3..fed01cc2 100644
--- a/qxt-mini/x11-keymap.cpp
+++ b/qxt-mini/x11-keymap.cpp
@@ -174,7 +174,6 @@ static tt numpad_keymap[] = {
{ Qt::Key_ScrollLock, XK_Scroll_Lock },
};
-QXT_GUI_EXPORT
quint32 qt_mods_to_x11(Qt::KeyboardModifiers modifiers)
{
quint32 mods = 0;
@@ -193,7 +192,6 @@ quint32 qt_mods_to_x11(Qt::KeyboardModifiers modifiers)
return mods;
}
-QXT_GUI_EXPORT
std::vector<quint32> qt_key_to_x11(Display*, Qt::Key k, Qt::KeyboardModifiers)
{
std::vector<quint32> ret;
@@ -212,10 +210,10 @@ std::vector<quint32> qt_key_to_x11(Display*, Qt::Key k, Qt::KeyboardModifiers)
return ret;
}
-QXT_GUI_EXPORT
+
Qt::KeyboardModifiers x11_mods_to_qt(quint32 mods)
{
- Qt::KeyboardModifiers ret(0);
+ int ret{0};
if (mods & Mod1Mask)
ret |= Qt::AltModifier;
@@ -226,10 +224,9 @@ Qt::KeyboardModifiers x11_mods_to_qt(quint32 mods)
if (mods & ShiftMask)
ret |= Qt::ShiftModifier;
- return ret;
+ return {ret};
}
-QXT_GUI_EXPORT
std::tuple<Qt::Key, Qt::KeyboardModifiers> x11_key_to_qt(Display* disp, quint32 keycode, quint32 mods)
{
(void)disp;
@@ -243,11 +240,10 @@ std::tuple<Qt::Key, Qt::KeyboardModifiers> x11_key_to_qt(Display* disp, quint32
return t(k, x11_mods_to_qt(mods));
}
- return t(Qt::Key(0), Qt::KeyboardModifiers(0));
+ return t(Qt::Key(0), Qt::KeyboardModifier(0));
}
-QXT_GUI_EXPORT
QPair<KeySym, KeySym> keycode_to_keysym(Display* disp,
quint32 keycode, quint32 keystate,
xcb_key_press_event_t const* kev)
@@ -282,7 +278,7 @@ QPair<KeySym, KeySym> keycode_to_keysym(Display* disp,
static char bytes[255+1];
KeySym sym = 0;
- int len = XLookupString(&ev, bytes, 255, &sym, NULL);
+ int len = XLookupString(&ev, bytes, 255, &sym, nullptr);
if (len <= 0 || len > 255)
{
len = 0;
@@ -298,10 +294,9 @@ QPair<KeySym, KeySym> keycode_to_keysym(Display* disp,
return ret;
}
-QXT_GUI_EXPORT
quint32 xcb_mods_to_x11(quint32 mods)
{
- unsigned int keystate = 0;
+ unsigned keystate = 0;
if(mods & XCB_MOD_MASK_1) // alt
keystate |= Mod1Mask;
diff --git a/qxt-mini/x11-keymap.hpp b/qxt-mini/x11-keymap.hpp
index 52e2ebea..a6f2a061 100644
--- a/qxt-mini/x11-keymap.hpp
+++ b/qxt-mini/x11-keymap.hpp
@@ -19,24 +19,20 @@
#include <X11/Xlib.h>
#include <X11/Xutil.h>
-QXT_EXPORT
-std::vector<quint32> qt_key_to_x11(Display* disp,
- Qt::Key k, Qt::KeyboardModifiers m);
+std::vector<quint32>
+qt_key_to_x11(Display* disp, Qt::Key k, Qt::KeyboardModifiers m);
-QXT_EXPORT
-std::tuple<Qt::Key, Qt::KeyboardModifiers> x11_key_to_qt(Display* disp,
- quint32 keycode, quint32 mods);
+std::tuple<Qt::Key, Qt::KeyboardModifiers>
+x11_key_to_qt(Display* disp, quint32 keycode, quint32 mods);
-QXT_EXPORT QPair<KeySym, KeySym> keycode_to_keysym(Display* disp,
- quint32 keycode, quint32 keystate,
- xcb_key_press_event_t const* kev);
+QPair<KeySym, KeySym>
+keycode_to_keysym(Display* disp,
+ quint32 keycode, quint32 keystate,
+ xcb_key_press_event_t const* kev);
-QXT_EXPORT quint32 qt_mods_to_x11(Qt::KeyboardModifiers modifiers);
+quint32 qt_mods_to_x11(Qt::KeyboardModifiers modifiers);
-QXT_GUI_EXPORT
Qt::KeyboardModifiers x11_mods_to_qt(quint32 mods);
-
-QXT_GUI_EXPORT
quint32 xcb_mods_to_x11(quint32 mods);
#endif
diff --git a/redist/x64/msvcp110.dll b/redist/x64/msvcp110.dll
new file mode 100644
index 00000000..7e278956
--- /dev/null
+++ b/redist/x64/msvcp110.dll
Binary files differ
diff --git a/redist/x64/msvcr110.dll b/redist/x64/msvcr110.dll
new file mode 100644
index 00000000..dd484a58
--- /dev/null
+++ b/redist/x64/msvcr110.dll
Binary files differ
diff --git a/redist/x86/msvcp110.dll b/redist/x86/msvcp110.dll
new file mode 100644
index 00000000..93fab568
--- /dev/null
+++ b/redist/x86/msvcp110.dll
Binary files differ
diff --git a/redist/x86/msvcr110.dll b/redist/x86/msvcr110.dll
new file mode 100644
index 00000000..1ce960d7
--- /dev/null
+++ b/redist/x86/msvcr110.dll
Binary files differ
diff --git a/sdk-paths-runner@GNU-Linux.cmake b/sdk-paths-runner@GNU-Linux.cmake
new file mode 100644
index 00000000..03c87ce4
--- /dev/null
+++ b/sdk-paths-runner@GNU-Linux.cmake
@@ -0,0 +1,3 @@
+set(SDK_WINE ON CACHE BOOL "" FORCE)
+set(OPENTRACK_WINE_ARCH -m64 CACHE STRING "" FORCE)
+set(ENV{PATH} "/usr/lib/wine:$ENV{PATH}")
diff --git a/sdk-paths-runneradmin@MSVC-windows.cmake b/sdk-paths-runneradmin@MSVC-windows.cmake
new file mode 100644
index 00000000..37f8edc8
--- /dev/null
+++ b/sdk-paths-runneradmin@MSVC-windows.cmake
@@ -0,0 +1,33 @@
+#
+# qtbase configure line for safekeeping
+#
+
+# "../configure" -prefix d:\dev\qt-5.10.0 -no-ico -no-gif \
+# -opengl desktop -no-fontconfig -no-harfbuzz \
+# -nomake tests -no-mp -release -opensource -shared -confirm-license \
+# -no-freetype -force-debug-info -separate-debug-info \
+# -make-tool jom -platform win32-msvc -static-runtime
+
+# remember to change -MD to -MT in mkspecs/
+# also add CFLAGS -Zi and LFLAGS -DEBUG
+
+set(__depdir "${CMAKE_CURRENT_LIST_DIR}/opentrack-depends")
+
+function(setq name value)
+ set("${name}" "${__depdir}/${value}" CACHE INTERNAL "" FORCE)
+endfunction()
+
+set(opentrack_install-debug-info FALSE CACHE INTERNAL "" FORCE)
+
+#setq(OpenCV_DIR "../opencv")
+#setq(SDK_ARUCO_LIBPATH "../aruco")
+#setq(SDK_FSUIPC "fsuipc")
+#setq(SDK_HYDRA "SixenseSDK")
+#setq(SDK_KINECT20 "../Kinect-v2.0")
+#setq(SDK_LIBUSB "libusb-msvc-x86")
+#setq(SDK_REALSENSE "../RSSDK-R2")
+#setq(SDK_RIFT_140 "ovr_sdk_win_23.0.0/LibOVR")
+#setq(SDK_VALVE_STEAMVR "steamvr")
+#setq(SDK_VJOYSTICK "vjoystick")
+#setq(ONNXRuntime_DIR "../onnxruntime-1.12.1")
+#setq(SDK_TRACKHAT_SENSOR "../trackhat-c-library-driver")
diff --git a/sdk-paths-sthalik@Clang-Linux.cmake b/sdk-paths-sthalik@Clang-Linux.cmake
new file mode 100644
index 00000000..fb6e64d9
--- /dev/null
+++ b/sdk-paths-sthalik@Clang-Linux.cmake
@@ -0,0 +1,55 @@
+set(opentrack_install-debug-info TRUE CACHE INTERNAL "" FORCE)
+
+add_compile_options(
+ -ferror-limit=1
+ -Weverything -Werror=implicit-function-declaration
+ -Werror
+ -Wno-c++11-compat -Wno-c++14-compat -Wno-c++98-compat-pedantic
+ -Wno-shadow -Wno-old-style-cast
+ -Wno-return-std-move-in-c++11 -Wno-exit-time-destructors -Wno-switch-enum
+ -Wno-global-constructors -Wno-missing-prototypes
+ -Wno-covered-switch-default -Wno-zero-length-array
+ -Wno-weak-vtables -Wno-weak-template-vtables
+ -Wno-padded -Wno-shadow-field -Wno-shorten-64-to-32
+ -Wno-sign-conversion
+ -Wno-extra-semi-stmt # for qt moc
+ -Wno-implicit-int-float-conversion
+ -Wno-alloca)
+
+set(base-flags "-Wall -Wextra -Wpedantic")
+
+set(CMAKE_LINKER "ld.lld")
+set(CMAKE_CXX_COMPILER "clang++")
+set(CMAKE_C_COMPILER "clang")
+
+set(CMAKE_C_FLAGS "-std=gnu11 ${base-flags} -ggdb ${CMAKE_C_FLAGS}")
+set(CMAKE_CXX_FLAGS "-std=c++17 ${base-flags} -ggdb ${CMAKE_CXX_FLAGS}")
+
+set(opt-flags "-O3 -ffast-math -march=native -flto=thin")
+
+set(CMAKE_BUILD_TYPE "RELEASE")
+set(CMAKE_CXX_FLAGS_RELEASE "${opt-flags} ${CMAKE_CXX_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_RELEASE "${opt-flags} ${CMAKE_C_FLAGS_RELEASE}")
+
+set(CMAKE_CXX_FLAGS_DEBUG "-ggdb ${CMAKE_CXX_FLAGS_DEBUG}")
+set(CMAKE_C_FLAGS_DEBUG "-ggdb ${CMAKE_C_FLAGS_DEBUG}")
+
+set(CMAKE_C_FLAGS "-ggdb")
+set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")
+
+add_compile_options("-emit-llvm")
+add_link_options("-fuse-ld=lld")
+
+foreach(x EXE MODULE SHARED)
+ set(CMAKE_${x}_LINKER_FLAGS "${CMAKE_${x}_LINKER_FLAGS}")
+endforeach()
+
+function(set_sdk var path)
+ set(${var} "$ENV{HOME}/dev/opentrack-depends/${path}" CACHE INTERNAL "" FORCE)
+endfunction()
+
+set_sdk(SDK_ARUCO_LIBPATH "aruco/build/src/libaruco.a")
+set_sdk(SDK_HYDRA "SixenseSDK")
+set_sdk(SDK_VALVE_STEAMVR "steamvr")
+set_sdk(SDK_WINE TRUE)
+set_sdk(SDK_XPLANE "X-Plane-SDK")
diff --git a/sdk-paths-sthalik@Clang-windows.cmake b/sdk-paths-sthalik@Clang-windows.cmake
new file mode 100644
index 00000000..97ad67ed
--- /dev/null
+++ b/sdk-paths-sthalik@Clang-windows.cmake
@@ -0,0 +1,70 @@
+#
+# qtbase configure line for safekeeping
+#
+
+# "../configure" -prefix d:\dev\qt-5.10.0 -no-ico -no-gif -no-libjpeg \
+# -opengl desktop -no-angle -no-fontconfig -no-harfbuzz \
+# -nomake tests -no-mp -release -opensource -shared -confirm-license \
+# -no-freetype -force-debug-fo -make-tool make.exe -platform win32-g++
+
+set(__depdir "${CMAKE_CURRENT_LIST_DIR}/../opentrack-depends")
+
+function(setq name value)
+ set("${name}" "${__depdir}/${value}" CACHE PATH "" FORCE)
+endfunction()
+
+set(opentrack_install-debug-info TRUE CACHE INTERNAL "" FORCE)
+
+#setq(OpenCV_DIR "opencv/build-mingw-64")
+setq(SDK_ARUCO_LIBPATH "aruco/build-mingw-w64/src/libaruco.a")
+
+setq(SDK_FSUIPC "fsuipc")
+setq(SDK_HYDRA "SixenseSDK")
+
+setq(SDK_RIFT_140 "LibOVR-140/build-mingw-w64")
+
+setq(SDK_VALVE_STEAMVR "steamvr")
+setq(SDK_TOBII_EYEX "Tobii-EyeX")
+setq(SDK_VJOYSTICK "vjoystick")
+
+setq(SDK_REALSENSE "RSSDK-R2")
+
+# WARNING: this is utter experimental nonsense
+set(_cxxflags
+ -Weverything
+
+ -Wno-global-constructors
+ -Wno-exit-time-destructors
+ -Wno-deprecated
+ -Wno-self-assign-overloaded
+ -Wno-double-promotion
+ -Wno-c++98-compat-pedantic
+ -Wno-old-style-cast
+ -Wno-shadow
+ -Wno-sign-conversion
+ -Wno-used-but-marked-unused
+ -Wno-covered-switch-default
+ -Wno-missing-prototypes
+ -Wno-padded
+ -Wno-switch-enum
+
+ -Werror
+ -Werror=inconsistent-missing-destructor-override
+ #-Wno-error=padded
+ -fdiagnostics-color=always
+
+ -fno-exceptions -fno-rtti # get rid of evil
+)
+set(base-cxxflags "")
+foreach(k ${_cxxflags})
+ set(base-cxxflags "${base-cxxflags} ${k}")
+endforeach()
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${base-cxxflags}")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${base-cxxflags}")
+
+set(CMAKE_CXX_FLAGS_RELEASE "-ffast-math ${CMAKE_CXX_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_RELEASE "-ffast-math ${CMAKE_C_FLAGS_RELEASE}")
+
+set(CMAKE_CXX_FLAGS_DEBUG "-ggdb ${CMAKE_CXX_FLAGS_DEBUG}")
+set(CMAKE_C_FLAGS_DEBUG "-ggdb ${CMAKE_C_FLAGS_DEBUG}")
diff --git a/sdk-paths-sthalik@GNU-windows.cmake b/sdk-paths-sthalik@GNU-windows.cmake
new file mode 100644
index 00000000..593f537f
--- /dev/null
+++ b/sdk-paths-sthalik@GNU-windows.cmake
@@ -0,0 +1,40 @@
+#
+# qtbase configure line for safekeeping
+#
+
+# "../configure" -prefix d:\dev\qt-5.10.0 -no-ico -no-gif -no-libjpeg \
+# -opengl desktop -no-angle -no-fontconfig -no-harfbuzz \
+# -nomake tests -no-mp -release -opensource -shared -confirm-license \
+# -no-freetype -force-debug-fo -make-tool make.exe -platform win32-g++
+
+set(__depdir "${CMAKE_CURRENT_LIST_DIR}/../opentrack-depends")
+
+function(setq name value)
+ set("${name}" "${__depdir}/${value}" CACHE PATH "" FORCE)
+endfunction()
+
+set(opentrack_install-debug-info TRUE CACHE INTERNAL "" FORCE)
+
+#setq(OpenCV_DIR "opencv/build-mingw-64")
+setq(SDK_ARUCO_LIBPATH "aruco/build-mingw-w64/src/libaruco.a")
+
+setq(SDK_FSUIPC "fsuipc")
+setq(SDK_HYDRA "SixenseSDK")
+
+setq(SDK_RIFT_140 "LibOVR-140/build-mingw-w64")
+
+setq(SDK_VALVE_STEAMVR "steamvr")
+setq(SDK_TOBII_EYEX "Tobii-EyeX")
+setq(SDK_VJOYSTICK "vjoystick")
+
+setq(SDK_REALSENSE "RSSDK-R2")
+
+set(base-flags "-Wall -Wextra -Wpedantic")
+set(CMAKE_C_FLAGS "-std=c11 ${base-flags} -ggdb ${CMAKE_C_FLAGS}")
+set(CMAKE_CXX_FLAGS "-std=c++17 ${base-flags} -ggdb ${CMAKE_CXX_FLAGS}")
+
+set(CMAKE_CXX_FLAGS_RELEASE "-O3 -ffast-math ${CMAKE_CXX_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_RELEASE "-O3 -ffast-math ${CMAKE_C_FLAGS_RELEASE}")
+
+set(CMAKE_CXX_FLAGS_DEBUG "-ggdb ${CMAKE_CXX_FLAGS_DEBUG}")
+set(CMAKE_C_FLAGS_DEBUG "-ggdb ${CMAKE_C_FLAGS_DEBUG}")
diff --git a/sdk-paths-sthalik@MSVC-windows.cmake b/sdk-paths-sthalik@MSVC-windows.cmake
index 57f336d1..187a5da8 100644
--- a/sdk-paths-sthalik@MSVC-windows.cmake
+++ b/sdk-paths-sthalik@MSVC-windows.cmake
@@ -1,32 +1,59 @@
+#
# qtbase configure line for safekeeping
-# "../configure" -prefix d:\dev\qt-5.10.0 -no-ico -no-gif -no-libjpeg -opengl desktop -no-angle -no-fontconfig -no-harfbuzz -nomake tests -no-mp -release -opensource -shared -confirm-license -no-freetype -force-debug-info -separate-debug-info -make-tool jom -platform win32-msvc
+#
+
+# "../configure" -prefix d:\dev\qt-5.10.0 -no-ico -no-gif \
+# -opengl desktop -no-fontconfig -no-harfbuzz \
+# -nomake tests -no-mp -release -opensource -shared -confirm-license \
+# -no-freetype -force-debug-info -separate-debug-info \
+# -make-tool jom -platform win32-msvc -static-runtime
# remember to change -MD to -MT in mkspecs/
# also add CFLAGS -Zi and LFLAGS -DEBUG
+set(__depdir "${CMAKE_CURRENT_LIST_DIR}/../opentrack-depends")
+
function(setq name value)
- set("${name}" "${value}" CACHE INTERNAL "" FORCE)
+ set("${name}" "${__depdir}/${value}" CACHE INTERNAL "" FORCE)
endfunction()
-set(Qt5_DIR "D:/dev/qt-5.10.0/lib/cmake/Qt5")
-set(__depdir "d:/dev/opentrack-depends/")
-set(OpenCV_DIR "${__depdir}/opencv/build")
-
-setq(opentrack_install-debug-info TRUE)
-
-setq(SDK_ARUCO_LIBPATH "${__depdir}/aruco/build/src/aruco.lib")
-setq(EIGEN3_INCLUDE_DIR "${__depdir}/eigen")
-
-setq(SDK_FSUIPC "${__depdir}/fsuipc")
-setq(SDK_HYDRA "${__depdir}/SixenseSDK")
-
-setq(SDK_RIFT_025 "${__depdir}/LibOVR-025/build")
-setq(SDK_RIFT_042 "${__depdir}/LibOVR-042/build")
-setq(SDK_RIFT_080 "${__depdir}/LibOVR-080/build")
-setq(SDK_RIFT_140 "${__depdir}/LibOVR-140/build")
-
-setq(SDK_VALVE_STEAMVR "${__depdir}/steamvr")
-setq(SDK_TOBII_EYEX "${__depdir}/Tobii-EyeX")
-setq(SDK_VJOYSTICK "${__depdir}/vjoystick")
-
-setq(SDK_REALSENSE "D:/RSSDK-R2")
+set(opentrack_install-debug-info TRUE CACHE INTERNAL "" FORCE)
+
+setq(SDK_RIFT_140 "ovr_sdk_win_23.0.0/LibOVR")
+setq(SDK_KINECT20 "nonfree/Kinect-v2.0")
+setq(SDK_VJOYSTICK "vjoystick")
+setq(SDK_PS3EYEDRIVER "PS3EYEDriver")
+setq(SDK_REALSENSE "nonfree/RSSDK-R2")
+setq(SDK_VALVE_STEAMVR "steamvr")
+setq(SDK_FSUIPC "fsuipc")
+setq(SDK_HYDRA "SixenseSDK")
+setq(SDK_EYEWARE_BEAM "eyeware-beam-sdk")
+setq(SDK_TOBII "nonfree/tobii.streamengine.native.2.2.2.363")
+if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+setq(Qt5_DIR "../qt-5.15-kde-amd64/lib/cmake/Qt5")
+setq(OpenCV_DIR "opencv/build-amd64/install")
+setq(SDK_ARUCO_LIBPATH "aruco/build-amd64/src/aruco.lib")
+setq(SDK_LIBUSB "libusb-msvc-amd64")
+setq(ONNXRuntime_DIR "onnxruntime-1.18.0-amd64")
+setq(SDK_TRACKHAT_SENSOR "trackhat-c-library-driver/build-amd64/install")
+setq(SDK_OSCPACK "oscpack/build-amd64")
+elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
+setq(Qt5_DIR "../qt/qt-5.15-kde-msvc-32/lib/cmake/Qt5")
+setq(OpenCV_DIR "opencv/build/install")
+setq(SDK_ARUCO_LIBPATH "aruco/build/src/aruco.lib")
+setq(SDK_LIBUSB "libusb-msvc-x86")
+setq(ONNXRuntime_DIR "onnxruntime-1.18.0-noavx")
+setq(SDK_TRACKHAT_SENSOR "trackhat-c-library-driver/build/install")
+setq(SDK_OSCPACK "oscpack/build")
+else()
+ message(FATAL_ERROR "unknown word size ${CMAKE_SIZEOF_VOID_P}")
+endif()
+
+set(CMAKE_ASM_NASM_COMPILER nasm.exe CACHE FILEPATH "" FORCE)
+
+set(Qt5Core_DIR "${Qt5_DIR}Core" CACHE PATH "" FORCE)
+set(Qt5Gui_DIR "${Qt5_DIR}Gui" CACHE PATH "" FORCE)
+
+if(CMAKE_GENERATOR STREQUAL "NMake Makefiles")
+ set(CMAKE_MAKE_PROGRAM "jom" CACHE STRING "" FORCE)
+endif()
diff --git a/settings/facetracknoir supported games.csv b/settings/facetracknoir supported games.csv
index 1be3b350..62e3ca23 100644
--- a/settings/facetracknoir supported games.csv
+++ b/settings/facetracknoir supported games.csv
@@ -76,7 +76,7 @@ No;Game Name;Game protocol;Supported since;Verified;By;INTERNATIONAL_ID;FTN_ID
84;Dark Horizons Lore;FreeTrack20;V160;;;7901;005487801A852AA7385200
86;Dawn of Aces II;FreeTrack20;V160;;;1302;00566925967C2BB0285500
87;DBS WalkAndFollow;FreeTrack20;V160;;;2525;005770A8B77008BDE89200
-88;DCS: Black Shark;FreeTrack20;V170;V;EmBeES;1006;0058F688FC9B0556868F00
+88;DCS;FreeTrack20;V170;V;EmBeES;1006;0058F688FC9B0556868F00
90;Dead Reckoning;FreeTrack20;V160;;;10401;005A4C2F6752F912D66200
91;Delivery;FreeTrack20;V160;;;15901;005BF3E85B1A8534AE3400
92;DiRT;FreeTrack20;V160;;;8104;005C72456F7523881F1800
@@ -624,3 +624,95 @@ No;Game Name;Game protocol;Supported since;Verified;By;INTERNATIONAL_ID;FTN_ID
758;F1 2016;FreeTrack20;V160;;;8111;02F62FF3D6F90370C8A300
759;SW Plugin;FreeTrack20;V160;;;20155;02F7E45153116EF485A500
760;Vector 36;FreeTrack20;V160;;;5325;02F8C318595317B9FF3B00
+761;Admiral Quality;FreeTrack20;V160;;;23135;02F91D1865C8E64BC74900
+762;Air Missions: HIND;FreeTrack20;V160;;;5350;02FAA58A3613B4C36BBA00
+763;Bogomoloov;FreeTrack20;V160;;;20970;02FB7CA00496C164330B00
+764;Bussim 18;FreeTrack20;V160;;;4777;02FC16C8650B60A6D1CB00
+765;DiRT 4;FreeTrack20;V160;;;8113;02FD4BFE2DCEF83F274F00
+766;DonNTU;FreeTrack20;V160;;;23140;02FE0612ABBBE91ED50500
+767;F1 2017;FreeTrack20;V160;;;8112;02FF0F0E9136983BE98300
+768;F1 2018;FreeTrack20;V160;;;8114;030008A86457E1E51C5D00
+769;Flying Tigers: Shadows Over China;FreeTrack20;V160;;;5351;0301AF83D5CD4199F9A700
+770;GazePlusMouse;FreeTrack20;V160;;;20991;0302FBE7C07B115B3BEE00
+771;Graphicacy;FreeTrack20;V160;;;7526;030367FF8F5F0F77E35900
+772;ISIR;FreeTrack20;V160;;;23130;030406334FD44B02DD9D00
+773;Karamyshev;FreeTrack20;V160;;;20143;0305AF0E18F430FC7E4E00
+774;LeoPoly;FreeTrack20;V160;;;20975;03067451494C34FDD25300
+775;Lorby;FreeTrack20;V160;;;23120;03074730C6F9065B6D0200
+776;Math Combat Challenge;FreeTrack20;V160;;;5425;0308497997AFA66179BA00
+777;Meridia;FreeTrack20;V160;;;20995;0309CF57272638BCCFD100
+778;NERVtel;FreeTrack20;V160;;;20127;030A8AAD0AEB797B18C200
+779;Off-Road paradise;FreeTrack20;V160;;;5450;030B998A4A810F2E84D200
+780;On the Road;FreeTrack20;V160;;;16025;030C941C3C76EB57197800
+781;Overload;FreeTrack20;V160;;;5475;030DA58A3AFFEC32661500
+782;Philips;FreeTrack20;V160;;;20147;030E069A0365E52B45E500
+783;Planet Nomads;FreeTrack20;V160;;;5375;030FEFAB27A2CF7C5F2600
+784;Plumley;FreeTrack20;V160;;;20137;03101675834ED281680300
+785;POC;FreeTrack20;V160;;;20985;0311854DF41E300146F900
+786;PULSAR: Lost Colony;FreeTrack20;V160;;;4100;03124B302C07D82E00F100
+787;Quatrefoil;FreeTrack20;V160;;;21000;0313078D1754A079D69900
+788;Raydon;FreeTrack20;V160;;;3702;0314FDF453C59676DB0B00
+789;Rebellion Developments;FreeTrack20;V160;;;7550;0315812C0E3DFCCA424800
+790;Rensseluer;FreeTrack20;V160;;;20980;031620B21441BEFAD08400
+791;RS Project;FreeTrack20;V160;;;20180;0317D8CBC90A182924DC00
+792;San Jose State;FreeTrack20;V160;;;20123;03181F4BBFD93140EC9400
+793;Skowronski;FreeTrack20;V160;;;20990;031921DED6943CD2DA9200
+794;Squad;FreeTrack20;V160;;;5525;031AE744F8E471F1C78200
+795;Train Sim World;FreeTrack20;V160;;;3854;031BA3103165DAA786A300
+796;Uni-Santiago;FreeTrack20;V160;;;23115;031C55CC449B8B2ED7F800
+797;Untitled Simbin;FreeTrack20;V160;;;3952;031D67F1F4F83F19405000
+798;VBS3 IG;FreeTrack20;V160;;;7507;031EB220C156D485EE6D00
+799;Weltjen;FreeTrack20;V160;;;20153;031F851F64EE45273D2E00
+800;WHSIL;FreeTrack20;V160;;;23125;0320F09528081A724FAD00
+801;Animech;FreeTrack20;V160;;;23200;032187D09D27D146691B00
+802;Brightrock;FreeTrack20;V160;;;8120;03224C095524F3EF5A8500
+803;CNU;FreeTrack20;V160;;;23255;032381B5E5281A79299800
+804;couby;FreeTrack20;V160;;;23190;03245BEE3F3A461B88CF00
+805;DayZ;FreeTrack20;V160;;;8135;0325AF804AA8E17DB87600
+806;Deblur;FreeTrack20;V160;;;23205;032674750FD1B63B4B1E00
+807;Diginext;FreeTrack20;V160;;;23165;0327E437168A93B7895500
+808;Doublethink;FreeTrack20;V160;;;23250;0328464ECD2ECEEADD9B00
+809;End Space;FreeTrack20;V160;;;23275;03298C6351EC0541B58900
+810;Go For Launch: Mercury;FreeTrack20;V160;;;23225;032A4139613E6C7910F200
+811;Hunting Wild Game;FreeTrack20;V160;;;23210;032B24B33A3DE9644AA600
+812;i3dix;FreeTrack20;V160;;;23265;032C186D9CC013EEF68400
+813;Inv3rsion;FreeTrack20;V160;;;23170;032D9C6EA428D6440D7A00
+814;Kitakyushu Univ;FreeTrack20;V160;;;23245;032E1E74FFF43002711200
+815;LWHVR;FreeTrack20;V160;;;23155;032FE902A041F663753A00
+816;MS;FreeTrack20;V160;;;23220;033057BE4ABA00F41B0000
+817;NPS;FreeTrack20;V160;;;23145;033119034A67900AE32000
+818;NTU;FreeTrack20;V160;;;23230;033242762D5CD9491F5C00
+819;OSSimTech;FreeTrack20;V160;;;23260;0333A3498349E6BD60C100
+820;SEAAT;FreeTrack20;V160;;;23185;0334B167A9690ED8F73300
+821;SKKU;FreeTrack20;V160;;;23215;0335EB1AF5882E3E5DCC00
+822;SUTD;FreeTrack20;V160;;;23160;033633F20953BCB826EF00
+823;Technicolor;FreeTrack20;V160;;;23270;03370882B65696DB3F1100
+824;Tecknotrove;FreeTrack20;V160;;;23240;0338E5656CCF17E25ACB00
+825;Tourist Bus Simulator;FreeTrack20;V160;;;8130;03395E485D939DF3C8E600
+826;Trans Sim;FreeTrack20;V160;;;23285;033AB1354A142CAB9F8F00
+827;UCL;FreeTrack20;V160;;;23150;033B9C235BD639F5855E00
+828;UMD;FreeTrack20;V160;;;23280;033C4CEE887A176D7B1D00
+829;Uni-Hamburg;FreeTrack20;V160;;;23195;033D1923F4C98158E4E300
+830;University of Korea;FreeTrack20;V160;;;23235;033E81D5E01D25F0258F00
+831;vJoy;FreeTrack20;V160;;;23175;033FBC6527D3DF2DA61600
+832;Vox Machinae;FreeTrack20;V160;;;8125;03408C95C383696F38EB00
+833;w-hs;FreeTrack20;V160;;;23180;0341D88F5D050D64BB1E00
+834;Assetto Corsa Competizione;FreeTrack20;V160;;;8140;0342E4E8A3BB50E1518200
+835;CFPT;FreeTrack20;V160;;;23290;0343A93E3293CAF36CE300
+836;Escape from Tarkov;FreeTrack20;V160;;;8155;0344F81AF665F954F06900
+837;FlyInside Flight Simulator;FreeTrack20;V160;;;8145;034570BE736C991E163800
+838;Heavy Duty Challenge;FreeTrack20;V160;;;8180;0346CEBAD1615B9B3DF000
+839;Hevelsan;FreeTrack20;V160;;;23315;0347456E97F23237C16400
+840;Infinity: Battlescape;FreeTrack20;V160;;;8165;0348762DABEF4CCF7C7D00
+841;MS Flight Simulator 2020;FreeTrack20;V160;;;8150;034918ED4805F1FA4B7200
+842;Project Wingman;FreeTrack20;V160;;;8185;034A674065C99859032300
+843;Pylon Racer;FreeTrack20;V160;;;23325;034BEB4A3DE88FF4196F00
+844;Reentry - An Orbital Simulator;FreeTrack20;V160;;;8160;034CF1BBC4316F6D883300
+845;Star Wars: Squadrons;FreeTrack20;V160;;;8175;034D4C65426F7385BC9300
+846;Stormworks: Build and Rescue;FreeTrack20;V160;;;8170;034EA8DFCAE674B5E23200
+847;TrackIR SDK;FreeTrack20;V160;;;1000;034FB5D001A7FFDB7FF700
+848;U of Craiova;FreeTrack20;V160;;;23310;0350EEED69C66DD80A0C00
+849;U of Tokyo;FreeTrack20;V160;;;23305;03519E5B8AFF6DC551D200
+850;X-Wing Alliance;FreeTrack20;V160;;;23320;03520929423DC2DC4FA900
+851;XIM;FreeTrack20;V160;;;23295;035347EF0E062076A28000
+852;YZView;FreeTrack20;V160;;;23300;03547AF86EBD6A9F0F4C00
diff --git a/spline/CMakeLists.txt b/spline/CMakeLists.txt
index 7d427601..731c0b6e 100644
--- a/spline/CMakeLists.txt
+++ b/spline/CMakeLists.txt
@@ -1,2 +1 @@
otr_module(spline BIN)
-target_link_libraries(opentrack-spline)
diff --git a/spline/axis-opts.cpp b/spline/axis-opts.cpp
index d7616939..a2b4941d 100644
--- a/spline/axis-opts.cpp
+++ b/spline/axis-opts.cpp
@@ -1,5 +1,7 @@
#include "axis-opts.hpp"
+namespace axis_opts_impl {
+
using max_clamp = axis_opts::max_clamp;
static max_clamp get_max_x(Axis k)
@@ -25,21 +27,24 @@ static max_clamp get_max_y(Axis k)
}
axis_opts::axis_opts(QString pfx, Axis idx) :
+ prefix_(pfx),
+ axis_(idx),
zero(b_settings_window, n(pfx, "zero-pos"), 0),
src(b_settings_window, n(pfx, "source-index"), idx),
- invert(b_settings_window, n(pfx, "invert-sign"), false),
+ invert_post(b_settings_window, n(pfx, "invert-sign-post"), false),
+ invert_pre(b_settings_window, n(pfx, "invert-sign"), false),
altp(b_mapping_window, n(pfx, "alt-axis-sign"), false),
clamp_x_(b_mapping_window, n(pfx, "max-value"), get_max_x(idx)),
- clamp_y_(b_mapping_window, n(pfx, "max-output-value"), get_max_y(idx)),
- prefix_(pfx),
- axis_(idx)
+ clamp_y_(b_mapping_window, n(pfx, "max-output-value"), get_max_y(idx))
{}
QString const& axis_opts::prefix() const { return prefix_; }
Axis axis_opts::axis() const { return axis_; }
-QString axis_opts::n(QString pfx, QString name)
+QString axis_opts::n(QString const& pfx, QString const& name)
{
return QString("%1-%2").arg(pfx, name);
}
+
+} // ns axis_opts_impl
diff --git a/spline/axis-opts.hpp b/spline/axis-opts.hpp
index 2f0d8432..437a5faa 100644
--- a/spline/axis-opts.hpp
+++ b/spline/axis-opts.hpp
@@ -9,8 +9,14 @@ namespace axis_opts_impl {
using namespace options;
-struct OTR_SPLINE_EXPORT axis_opts final
+class OTR_SPLINE_EXPORT axis_opts final
{
+ QString prefix_;
+ Axis axis_;
+
+ static inline QString n(QString const& pfx, QString const& name);
+
+public:
enum max_clamp
{
r180 = 180,
@@ -23,7 +29,11 @@ struct OTR_SPLINE_EXPORT axis_opts final
r15 = 15,
r10 = 10,
+ t600 = 600,
+ t300 = 300,
+ t150 = 150,
t100 = 100,
+ t75 = 75,
t30 = 30,
t20 = 20,
t15 = 15,
@@ -32,16 +42,20 @@ struct OTR_SPLINE_EXPORT axis_opts final
o_r180 = -180,
o_r90 = -90,
o_t75 = -75,
+ o_t100 = -100,
+ o_t150 = -150,
+ o_t300 = -300,
+ o_t600 = -600,
x1000 = 1000,
};
// note, these two bundles can be the same value with no issues
- bundle b_settings_window = make_bundle("opentrack-ui");
- bundle b_mapping_window = make_bundle("opentrack-mappings");
+ bundle b_settings_window{ make_bundle(axis_ == Axis(-1) ? QString() : "opentrack-ui") };
+ bundle b_mapping_window{ make_bundle(axis_ == Axis(-1) ? QString() : "opentrack-mappings") };
value<double> zero;
value<int> src;
- value<bool> invert, altp;
+ value<bool> invert_pre, invert_post, altp;
value<max_clamp> clamp_x_, clamp_y_;
double max_clamp_x() const { return std::fabs(clamp_x_.to<double>()); }
double max_clamp_y() const { return std::fabs(clamp_y_.to<double>()); }
@@ -49,13 +63,8 @@ struct OTR_SPLINE_EXPORT axis_opts final
QString const& prefix() const;
Axis axis() const;
-private:
- static inline QString n(QString pfx, QString name);
-
- QString prefix_;
- Axis axis_;
};
} // ns axis_opts_impl
-using axis_opts_impl::axis_opts;
+using axis_opts = axis_opts_impl::axis_opts;
diff --git a/spline/broken/qfunctionconfiguratorplugin.h b/spline/broken/qfunctionconfiguratorplugin.h
index ca68e0e2..11b9a58c 100644
--- a/spline/broken/qfunctionconfiguratorplugin.h
+++ b/spline/broken/qfunctionconfiguratorplugin.h
@@ -16,7 +16,7 @@ class QFunctionConfiguratorPlugin : public QObject, public QDesignerCustomWidget
Q_INTERFACES(QDesignerCustomWidgetInterface)
public:
- QFunctionConfiguratorPlugin(QObject *parent = 0);
+ QFunctionConfiguratorPlugin(QObject *parent = nullptr);
bool isContainer() const;
bool isInitialized() const;
diff --git a/spline/lang/de_DE.ts b/spline/lang/de_DE.ts
new file mode 100644
index 00000000..1552582e
--- /dev/null
+++ b/spline/lang/de_DE.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+</TS>
diff --git a/spline/lang/zh_CN.ts b/spline/lang/zh_CN.ts
index 6401616d..e5ca8aa9 100644
--- a/spline/lang/zh_CN.ts
+++ b/spline/lang/zh_CN.ts
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
</TS>
diff --git a/spline/spline-widget.cpp b/spline/spline-widget.cpp
index 638b67a7..46e2095c 100644
--- a/spline/spline-widget.cpp
+++ b/spline/spline-widget.cpp
@@ -1,62 +1,59 @@
-/* Copyright (c) 2012-2016 Stanislaw Halik <sthalik@misaki.pl>
- *
- * 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 "spline-widget.hpp"
#include "compat/math.hpp"
+
+#include <algorithm>
+
#include <QPainter>
#include <QPainterPath>
-#include <QPaintEvent>
-#include <QPen>
#include <QPixmap>
-#include <QList>
-#include <QPoint>
#include <QString>
-#include <QRect>
-#include <QApplication>
-#include <QStyle>
-#include <QMouseEvent>
+#include <QToolTip>
+#include <QtEvents>
+#include <QPainterPath>
#include <QDebug>
-#include <cmath>
-#include <algorithm>
+#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
+# define OTR_OBSOLETE_QT_WORKAROUND
+#endif
+
+namespace spline_detail {
spline_widget::spline_widget(QWidget *parent) : QWidget(parent)
{
setMouseTracking(true);
- setFocusPolicy(Qt::ClickFocus);
+ //setFocusPolicy(Qt::ClickFocus);
setCursor(Qt::ArrowCursor);
}
spline_widget::~spline_widget()
{
if (connection)
+ {
QObject::disconnect(connection);
+ connection = {};
+ }
}
-void spline_widget::setConfig(base_spline* spl)
+void spline_widget::set_config(base_spline* spl)
{
if (connection)
{
QObject::disconnect(connection);
- connection = QMetaObject::Connection();
+ connection = {};
}
- _config = spl;
+ config = spl;
if (spl)
{
- update_range();
-
- std::shared_ptr<base_spline::base_settings> s = spl->get_settings();
- connection = connect(s.get(), &spline::base_settings::recomputed,
- this, [this]() { reload_spline(); },
+ std::shared_ptr<base_settings> s = spl->get_settings();
+ connection = connect(&*s, &base_settings::recomputed,
+ this, [this] { reload_spline(); },
Qt::QueuedConnection);
}
+
+ reload_spline();
}
QColor spline_widget::colorBezier() const
@@ -64,7 +61,7 @@ QColor spline_widget::colorBezier() const
return spline_color;
}
-void spline_widget::setColorBezier(QColor color)
+void spline_widget::setColorBezier(QColor const& color)
{
spline_color = color;
repaint();
@@ -72,37 +69,38 @@ void spline_widget::setColorBezier(QColor color)
void spline_widget::force_redraw()
{
- _background = QPixmap();
+ background_img = {};
repaint();
}
void spline_widget::set_preview_only(bool val)
{
- _preview_only = val;
+ preview_only = val;
}
bool spline_widget::is_preview_only() const
{
- return _preview_only;
+ return preview_only;
}
void spline_widget::drawBackground()
{
- QPainter painter(&_background);
+ QPainter painter(&background_img);
painter.fillRect(rect(), widget_bg_color);
{
QColor bg_color(112, 154, 209);
- if (!isEnabled() && !_preview_only)
+ if (!isEnabled() && !preview_only)
bg_color = QColor(176,176,180);
painter.fillRect(pixel_bounds, bg_color);
}
QFont font;
font.setPointSize(8);
+ font.setStyleHint(QFont::Monospace, QFont::PreferAntialias);
painter.setFont(font);
- QFontMetrics metrics(font);
+ const QFontMetricsF metrics(font);
QColor color__(176, 190, 209, 127);
@@ -111,66 +109,80 @@ void spline_widget::drawBackground()
const QPen pen(color__, 1, Qt::SolidLine, Qt::FlatCap);
- const int ystep = _y_step, xstep = _x_step;
- const qreal maxx = _config->max_input();
- const qreal maxy = _config->max_output();
+ const int ystep = (int)std::ceil(y_step_), xstep = (int)std::ceil(x_step_);
+ const double maxx = config->max_input();
+ const double maxy = config->max_output();
+#ifndef OTR_OBSOLETE_QT_WORKAROUND
+ double space_width = metrics.horizontalAdvance(' ');
+#else
+ double space_width = metrics.boundingRect(' ').right();
+#endif
+ painter.setPen(palette().text().color());
- // horizontal grid
+ // vertical grid
for (int i = 0; i <= maxy; i += ystep)
{
- const int y = int(pixel_bounds.height() - i * c.y() + pixel_bounds.y());
+ const double y = pixel_bounds.height() - i * c.y() + pixel_bounds.y();
drawLine(painter,
- QPoint(pixel_bounds.x(), y),
- QPoint(pixel_bounds.x() + pixel_bounds.width(), y),
+ QPointF(pixel_bounds.x(), y),
+ QPointF(pixel_bounds.x() + pixel_bounds.width(), y),
pen);
- painter.drawText(QRectF(10,
- y - metrics.height()/2.,
- pixel_bounds.left(),
- metrics.height()),
- QString::number(i));
+ QString text = QString::number(i);
+ QRectF rect = metrics.boundingRect(text);
+#ifndef OTR_OBSOLETE_QT_WORKAROUND
+ double advance = metrics.horizontalAdvance(text);
+#else
+ double advance = rect.right();
+#endif
+ painter.drawText(QPointF(pixel_bounds.x() - advance - space_width,
+ y - rect.height()/2 - rect.top()),
+ text);
}
- // vertical grid
+ // horizontal grid
for (int i = 0; i <= maxx; i += xstep)
{
- const int x = iround(pixel_bounds.x() + i * c.x());
+ const double x = pixel_bounds.x() + i * c.x();
drawLine(painter,
- QPoint(x, pixel_bounds.y()),
- QPoint(x, pixel_bounds.y() + pixel_bounds.height()),
+ QPointF(x, pixel_bounds.y()),
+ QPointF(x, pixel_bounds.y() + pixel_bounds.height()),
pen);
+
const QString text = QString::number(i);
- painter.drawText(QRectF(x - metrics.width(text)/2.,
- pixel_bounds.height() + 10 + metrics.height(),
- metrics.width(text),
- metrics.height()),
+ QRectF rect = metrics.boundingRect(text);
+#ifndef OTR_OBSOLETE_QT_WORKAROUND
+ double advance = metrics.horizontalAdvance(text);
+#else
+ double advance = metrics.boundingRect(text).right();
+#endif
+ painter.drawText(QPointF(x - advance/2 - rect.left(),
+ pixel_bounds.bottom() + metrics.lineSpacing()),
text);
}
}
void spline_widget::drawFunction()
{
- QPainter painter(&_function);
+ QPainter painter(&spline_img);
painter.setRenderHint(QPainter::Antialiasing, true);
- const points_t points = _config->get_points();
+ const points_t& points = config->get_points();
if (moving_control_point_idx >= 0 &&
moving_control_point_idx < points.size())
{
const QPen pen(Qt::white, 1, Qt::SolidLine, Qt::FlatCap);
- const QPointF prev_ = point_to_pixel(QPointF(0, 0));
- QPoint prev(iround(prev_.x()), iround(prev_.y()));
- for (int i = 0; i < points.size(); i++)
+ QPointF prev = point_to_pixel({});
+ for (const auto& point : points)
{
- const QPointF tmp_ = point_to_pixel(points[i]);
- const QPoint tmp(iround(tmp_.x()), iround(tmp_.y()));
+ const QPointF tmp = point_to_pixel(point);
drawLine(painter, prev, tmp, pen);
prev = tmp;
}
}
const QColor color_ = progn(
- if (!isEnabled() && !_preview_only)
+ if (!isEnabled() && !preview_only)
{
QColor color(spline_color);
const int avg = int(float(color.red() + color.green() + color.blue())/3);
@@ -187,57 +199,58 @@ void spline_widget::drawFunction()
}
);
- painter.setPen(QPen(color_, 1.75, Qt::SolidLine, Qt::FlatCap));
+ painter.setPen(QPen(color_, 2, Qt::SolidLine, Qt::FlatCap));
-//#define DEBUG_SPLINE
-#ifndef DEBUG_SPLINE
- constexpr double step_ = 5;
-
- const double maxx = _config->max_input();
- const double step = step_ / c.x();
+ const double dpr = screen_dpi();
+ const double line_length_pixels = std::fmax(1, dpr);
+ const double step = std::fmax(.1, line_length_pixels / c.x());
+ const double maxx = config->max_input();
+#define USE_CUBIC_SPLINE
+#if defined USE_CUBIC_SPLINE
QPainterPath path;
- path.moveTo(point_to_pixel(QPointF(0, 0)));
+ path.moveTo(point_to_pixel({}));
- const double max_x_pixel = point_to_pixel_(QPointF(maxx, 0)).x();
+ const double max_x_pixel = point_to_pixel({maxx, 0}).x();
- auto check = [=](const QPointF& val) {
- return val.x() < max_x_pixel
+ auto clamp = [=](const QPointF& val) {
+ return val.x() <= max_x_pixel
? val
- : QPointF(max_x_pixel, val.y());
+ : QPointF{max_x_pixel, val.y()};
};
- for (double k = 0; k < maxx; k += step*3)
- {
- const float next_1(_config->get_value_no_save(k + step*1));
- const float next_2(_config->get_value_no_save(k + step*2));
- const float next_3(_config->get_value_no_save(k + step*3));
+ const auto fn = [&] (double k) {
+ const auto next_1 = config->get_value_no_save(k + step*1);
+ const auto next_2 = config->get_value_no_save(k + step*2);
+ const auto next_3 = config->get_value_no_save(k + step*3);
- QPointF b(check(point_to_pixel_(QPointF(k + step*1, qreal(next_1))))),
- c(check(point_to_pixel_(QPointF(k + step*2, qreal(next_2))))),
- d(check(point_to_pixel_(QPointF(k + step*3, qreal(next_3)))));
+ QPointF b(clamp(point_to_pixel({k + step*1, next_1}))),
+ c(clamp(point_to_pixel({k + step*2, next_2}))),
+ d(clamp(point_to_pixel({k + step*3, next_3})));
- path.cubicTo(b, c, d);
- }
+ path.cubicTo(b, c, d);
+ };
+
+ for (double k = 0; k < maxx; k += step*3) // NOLINT
+ fn(k);
+
+ fn(maxx);
painter.drawPath(path);
#else
- constexpr int line_length_pixels = 3;
- const qreal max = _config->maxInput();
- const qreal step = clamp(line_length_pixels / c.x(), 5e-2, max);
- QPointF prev = point_to_pixel(QPoint(0, 0));
- for (qreal i = 0; i < max; i += step)
+ QPointF prev = point_to_pixel({});
+ for (double i = 0; i < maxx; i += step) // NOLINT
{
- const qreal val = qreal(_config->get_value_no_save(i));
- const QPointF cur = point_to_pixel(QPointF(i, val));
+ const auto val = config->get_value_no_save(i);
+ const QPointF cur = point_to_pixel({i, val});
painter.drawLine(prev, cur);
prev = cur;
}
{
- const qreal maxx = _config->maxInput();
- const qreal maxy = qreal(_config->get_value_no_save(maxx));
- painter.drawLine(QPointF(prev), point_to_pixel_(QPointF(maxx, maxy)));
+ const double maxx = config->max_input();
+ const double maxy = config->get_value_no_save(maxx);
+ painter.drawLine(prev, point_to_pixel({ maxx, maxy }));
}
#endif
@@ -250,51 +263,59 @@ void spline_widget::drawFunction()
painter.fillRect(r2, widget_bg_color);
const int alpha = !isEnabled() ? 64 : 120;
- if (!_preview_only)
+ if (!preview_only)
{
- for (int i = 0; i < points.size(); i++)
+ for (auto const& point : points)
{
drawPoint(painter,
- point_to_pixel(points[i]),
+ point_to_pixel(point),
QColor(200, 200, 210, alpha),
isEnabled() ? QColor(50, 100, 120, 200) : QColor(200, 200, 200, 96));
}
}
}
+QSize spline_widget::minimumSizeHint() const
+{
+ const double dpi = screen_dpi();
+ return { iround(800 * dpi), iround(250 * dpi) };
+}
+
void spline_widget::paintEvent(QPaintEvent *e)
{
- if (!_config)
+ if (!config)
return;
QPainter p(this);
const double dpr = devicePixelRatioF();
- const int W = int(width() * dpr);
- const int H = int(height() * dpr);
+ const int W = iround(width() * dpr);
+ const int H = iround(height() * dpr);
- if (_background.size() != QSize(W, H))
+ if (background_img.size() != QSize(W, H))
{
- _background = QPixmap(W, H);
- _background.setDevicePixelRatio(dpr);
- _draw_function = true;
+ update_range();
+
+ background_img = { W, H };
+ background_img.setDevicePixelRatio(dpr);
drawBackground();
+ draw_function = true;
}
- if (_draw_function)
+ if (draw_function)
{
- _draw_function = false;
- _function = _background;
+ draw_function = false;
+ spline_img = background_img;
drawFunction();
}
- p.drawPixmap(e->rect(), _function);
+ p.drawPixmap(e->rect(), spline_img);
// If the Tracker is active, the 'Last Point' it requested is recorded.
// Show that point on the graph, with some lines to assist.
// This new feature is very handy for tweaking the curves!
QPointF last;
- if (_config->get_last_value(last) && isEnabled())
+ if (config->get_last_value(last) && isEnabled())
drawPoint(p, point_to_pixel(last), QColor(255, 0, 0, 120));
}
@@ -303,13 +324,13 @@ void spline_widget::drawPoint(QPainter& painter, const QPointF& pos, const QColo
painter.save();
painter.setPen(QPen(border, 1, Qt::SolidLine, Qt::PenCapStyle::FlatCap));
painter.setBrush(colBG);
- painter.drawEllipse(QRectF(pos.x() - point_size,
- pos.y() - point_size,
- point_size*2, point_size*2));
+ painter.drawEllipse(QRectF{pos.x() - point_size_in_pixels,
+ pos.y() - point_size_in_pixels,
+ point_size_in_pixels*2, point_size_in_pixels*2});
painter.restore();
}
-void spline_widget::drawLine(QPainter& painter, const QPoint& start, const QPoint& end, const QPen& pen)
+void spline_widget::drawLine(QPainter& painter, const QPointF& start, const QPointF& end, const QPen& pen)
{
painter.save();
painter.setPen(pen);
@@ -320,17 +341,14 @@ void spline_widget::drawLine(QPainter& painter, const QPoint& start, const QPoin
void spline_widget::mousePressEvent(QMouseEvent *e)
{
- if (!_config || !isEnabled() || !is_in_bounds(e->pos()) || _preview_only)
- {
- clearFocus();
+ if (!config || !isEnabled() || !is_in_bounds(e->localPos()) || preview_only)
return;
- }
- const int point_pixel_closeness_limit = get_closeness_limit();
+ const double min_dist = min_pt_distance();
moving_control_point_idx = -1;
- points_t points = _config->get_points();
+ const points_t& points = config->get_points();
if (e->button() == Qt::LeftButton)
{
@@ -338,7 +356,7 @@ void spline_widget::mousePressEvent(QMouseEvent *e)
for (int i = 0; i < points.size(); i++)
{
- if (point_within_pixel(points[i], e->pos()))
+ if (point_within_pixel(points[i], e->localPos()))
{
is_touching_point = true;
moving_control_point_idx = i;
@@ -349,13 +367,12 @@ void spline_widget::mousePressEvent(QMouseEvent *e)
if (!is_touching_point)
{
bool too_close = false;
- const QPoint pos = e->pos();
+ const QPointF pos = pixel_to_point(e->localPos());
- for (int i = 0; i < points.size(); i++)
+ for (QPointF const& point : points)
{
- const QPointF pt = point_to_pixel(points[i]);
- const qreal x = std::fabs(pt.x() - pos.x());
- if (point_pixel_closeness_limit >= x)
+ const double x = std::fabs(point.x() - pos.x());
+ if (min_dist > x)
{
too_close = true;
break;
@@ -364,93 +381,91 @@ void spline_widget::mousePressEvent(QMouseEvent *e)
if (!too_close)
{
- _config->add_point(pixel_coord_to_point(e->pos()));
+ config->add_point(pixel_to_point(e->localPos()));
show_tooltip(e->pos());
}
}
- _draw_function = true;
+ draw_function = true;
}
if (e->button() == Qt::RightButton)
{
- if (_config)
+ if (config)
{
- int found_pt = -1;
for (int i = 0; i < points.size(); i++)
{
- if (point_within_pixel(points[i], e->pos()))
+ if (point_within_pixel(points[i], e->localPos()))
{
- found_pt = i;
+ config->remove_point(i);
+ draw_function = true;
break;
}
}
-
- if (found_pt != -1)
- {
- _config->remove_point(found_pt);
- _draw_function = true;
- }
}
}
- if (_draw_function)
+ if (draw_function)
repaint();
}
void spline_widget::mouseMoveEvent(QMouseEvent *e)
{
- if (_preview_only && _config)
+ if (preview_only && config)
{
show_tooltip(e->pos());
- clearFocus();
return;
}
- if (!_config || !isEnabled() || !isActiveWindow() || (moving_control_point_idx != -1 && !hasFocus()))
+ if (!config || !isEnabled() || !isActiveWindow())
{
- clearFocus();
+ QToolTip::hideText();
return;
}
const int i = moving_control_point_idx;
- const points_t points = _config->get_points();
+ const points_t& points = config->get_points();
const int sz = points.size();
if (i >= 0 && i < sz)
{
- const int point_closeness_limit = get_closeness_limit();
- bool overlap = false;
+ const double min_dist = min_pt_distance();
+ QPointF new_pt = pixel_to_point(e->localPos());
+
+ const bool has_prev = i > 0, has_next = i + 1 < points.size();
- const QPointF pix_ = point_to_pixel(pixel_coord_to_point(e->pos()));
- const QPoint pix(int(pix_.x()), int(pix_.y()));
+ auto check_next = [&] {
+ return points[i+1].x() - new_pt.x() >= min_dist;
+ };
- QPointF new_pt = pixel_coord_to_point(e->pos());
+ auto check_prev = [&] {
+ return new_pt.x() - points[i-1].x() >= min_dist;
+ };
- if (i + 1 < points.size())
+ if (has_prev && !check_prev())
{
- overlap |= points[i+1].x() - new_pt.x() < point_closeness_limit;
+ new_pt.rx() = points[i-1].x() + min_dist + 1e-4;
}
- if (i > 0)
+
+ if (has_next && !check_next())
{
- overlap |= new_pt.x() - points[i-1].x() < point_closeness_limit;
+ new_pt.rx() = points[i+1].x() - min_dist - 1e-4;
}
- if (overlap)
- new_pt = QPointF(points[i].x(), new_pt.y());
-
- _config->move_point(i, new_pt);
-
- _draw_function = true;
- repaint();
-
setCursor(Qt::ClosedHandCursor);
- show_tooltip(pix, new_pt);
+ show_tooltip(point_to_pixel(new_pt).toPoint(), new_pt);
+
+ if ((!has_prev || check_prev()) && (!has_next || check_next()))
+ {
+ config->move_point(i, new_pt);
+ draw_function = true;
+ repaint();
+ }
}
else if (sz)
{
int i;
- bool is_on_point = is_on_pt(e->pos(), &i);
+ bool is_on_point = is_on_pt(e->localPos(), &i);
if (is_on_point)
{
@@ -460,7 +475,7 @@ void spline_widget::mouseMoveEvent(QMouseEvent *e)
else
{
setCursor(Qt::ArrowCursor);
- if (is_in_bounds(e->pos()))
+ if (is_in_bounds(e->localPos()))
show_tooltip(e->pos());
else
QToolTip::hideText();
@@ -470,11 +485,8 @@ void spline_widget::mouseMoveEvent(QMouseEvent *e)
void spline_widget::mouseReleaseEvent(QMouseEvent *e)
{
- if (!_config || !isEnabled() || !isActiveWindow() || !hasFocus() || _preview_only)
- {
- clearFocus();
+ if (!config || !isEnabled() || !isActiveWindow() || preview_only)
return;
- }
const bool redraw = moving_control_point_idx != -1;
moving_control_point_idx = -1;
@@ -482,13 +494,13 @@ void spline_widget::mouseReleaseEvent(QMouseEvent *e)
if (e->button() == Qt::LeftButton)
{
{
- if (is_on_pt(e->pos(), nullptr))
+ if (is_on_pt(e->localPos(), nullptr))
setCursor(Qt::CrossCursor);
else
setCursor(Qt::ArrowCursor);
}
- if (is_in_bounds(e->pos()))
+ if (is_in_bounds(e->localPos()))
show_tooltip(e->pos());
else
QToolTip::hideText();
@@ -496,7 +508,7 @@ void spline_widget::mouseReleaseEvent(QMouseEvent *e)
if (redraw)
{
- _draw_function = true;
+ draw_function = true;
repaint();
}
}
@@ -504,49 +516,53 @@ void spline_widget::mouseReleaseEvent(QMouseEvent *e)
void spline_widget::reload_spline()
{
// don't recompute here as the value's about to be recomputed in the callee
- update_range();
+ background_img = {};
update();
}
-int spline_widget::get_closeness_limit()
+double spline_widget::min_pt_distance() const
{
- return iround(std::fmax(snap_x, 1));
+ double pt = 3*point_size_in_pixels / c.x();
+ pt = snap(pt, snap_x);
+ return pt;
}
void spline_widget::show_tooltip(const QPoint& pos, const QPointF& value_)
{
- const QPointF value = QPoint(0, 0) == value_ ? pixel_coord_to_point(pos) : value_;
+ const QPointF value = value_.isNull() ? pixel_to_point(pos) : value_;
double x = value.x(), y = value.y();
- if (_preview_only)
- y = _config->get_value_no_save(x);
+ if (preview_only)
+ y = config->get_value_no_save(x);
const int x_ = iround(x), y_ = iround(y);
- using std::fabs;
-
- if (fabs(x_ - x) < 1e-3)
+ if (std::fabs(x_ - x) < 1e-3)
x = x_;
- if (fabs(y_ - y) < 1e-3)
+ if (std::fabs(y_ - y) < 1e-3)
y = y_;
- const bool is_fusion = QStringLiteral("fusion") == QApplication::style()->objectName();
- const int add_x = (is_fusion ? 25 : 0), add_y = (is_fusion ? 15 : 0);
+ // the native OSX style doesn't look right otherwise
+#if defined __APPLE__
+ constexpr int off_x = 0, off_y = 0;
+#else
+ constexpr int off_x = 25, off_y = 15;
+#endif
- const QPoint pix(pos.x() + add_x, pos.y() + add_y);
+ const QPoint pix(pos.x() + off_x, pos.y() + off_y);
QToolTip::showText(mapToGlobal(pix),
- QStringLiteral("value: %1x%2").arg(x, 0, 'f', 2).arg(y, 0, 'f', 2),
+ QString{"value: %1x%2"}.arg(x, 0, 'f', 2).arg(y, 0, 'f', 2),
this,
rect(),
0);
}
-bool spline_widget::is_in_bounds(const QPoint& pos) const
+bool spline_widget::is_in_bounds(const QPointF& pos) const
{
- constexpr int grace = point_size * 3;
- constexpr int bottom_grace = int(point_size * 1.5);
+ const int grace = (int)std::ceil(point_size_in_pixels * 3);
+ const int bottom_grace = (int)std::ceil(point_size_in_pixels * 1.5);
return (pos.x() + grace > pixel_bounds.left() &&
pos.x() - grace < pixel_bounds.right() &&
pos.y() + grace > pixel_bounds.top() &&
@@ -555,27 +571,28 @@ bool spline_widget::is_in_bounds(const QPoint& pos) const
void spline_widget::update_range()
{
- if (!_config)
+ if (!config)
return;
const int w = width(), h = height();
const int mwl = 40, mhl = 20;
const int mwr = 15, mhr = 35;
- pixel_bounds = QRect(mwl, mhl, (w - mwl - mwr), (h - mhl - mhr));
- c = QPointF(pixel_bounds.width() / _config->max_input(), pixel_bounds.height() / _config->max_output());
- _draw_function = true;
-
- _background = QPixmap();
- _function = QPixmap();
+ pixel_bounds = { mwl, mhl, (w - mwl - mwr), (h - mhl - mhr) };
+ c = {
+ pixel_bounds.width() / std::fmax(1, config->max_input()),
+ pixel_bounds.height() / std::fmax(1, config->max_output())
+ };
- repaint();
+ draw_function = true;
+ background_img = {};
+ spline_img = {};
}
-bool spline_widget::point_within_pixel(const QPointF& pt, const QPoint &pixel)
+bool spline_widget::point_within_pixel(const QPointF& pt, const QPointF& pixel)
{
const QPointF tmp = pixel - point_to_pixel(pt);
- return QPointF::dotProduct(tmp, tmp) < point_size * point_size;
+ return QPointF::dotProduct(tmp, tmp) < point_size_in_pixels * point_size_in_pixels;
}
void spline_widget::focusOutEvent(QFocusEvent* e)
@@ -583,76 +600,66 @@ void spline_widget::focusOutEvent(QFocusEvent* e)
if (moving_control_point_idx != -1)
QToolTip::hideText();
moving_control_point_idx = -1;
- _draw_function = true;
+ draw_function = true;
lower();
setCursor(Qt::ArrowCursor);
e->accept();
}
-QPointF spline_widget::pixel_coord_to_point(const QPoint& point)
+double spline_widget::snap(double x, double snap_value)
{
- qreal x = (point.x() - pixel_bounds.x()) / c.x();
- qreal y = (pixel_bounds.height() - point.y() + pixel_bounds.y()) / c.y();
-
- constexpr int c = 1000;
-
- if (snap_x > 0)
+ if (snap_value > 0)
{
- x += snap_x * .5;
- x -= std::fmod(x, snap_x);
+ constexpr int c = 1000;
+ x += snap_value * .5;
+ x -= std::fmod(x, snap_value);
// truncate after few decimal places to reduce rounding errors.
- // round upward to nearest.
+ // round upward.
x = int(x * c + .5/c) / double(c);
}
- if (snap_y > 0)
- {
- y += snap_y * .5;
- y -= std::fmod(y, snap_y);
- // idem
- y = int(y * c + .5/c) / double(c);
- }
-
- if (x < 0)
- x = 0;
- if (x > _config->max_input())
- x = _config->max_input();
-
- if (y < 0)
- y = 0;
- if (y > _config->max_output())
- y = _config->max_output();
- return QPointF(x, y);
+ return x;
}
-QPointF spline_widget::point_to_pixel_(const QPointF& point)
+QPointF spline_widget::pixel_to_point(const QPointF& point)
{
- return QPointF(pixel_bounds.x() + point.x() * c.x(),
- pixel_bounds.y() + pixel_bounds.height() - point.y() * c.y());
+ double x = (point.x() - pixel_bounds.x()) / c.x();
+ double y = (pixel_bounds.height() - point.y() + pixel_bounds.y()) / c.y();
+
+ if (snap_x > 0)
+ x = snap(x, snap_x);
+ if (snap_y > 0)
+ y = snap(y, snap_y);
+
+ x = std::clamp(x, 0., config->max_input());
+ y = std::clamp(y, 0., config->max_output());
+
+ return { x, y };
}
-QPoint spline_widget::point_to_pixel(const QPointF& point)
+QPointF spline_widget::point_to_pixel(const QPointF& point)
{
- QPointF pt(point_to_pixel_(point));
-
- return QPoint(iround(pt.x()), iround(pt.y()));
+ return {
+ pixel_bounds.x() + point.x() * c.x(),
+ pixel_bounds.y() + pixel_bounds.height() - point.y() * c.y()
+ };
}
void spline_widget::resizeEvent(QResizeEvent *)
{
- update_range();
+ reload_spline();
}
-bool spline_widget::is_on_pt(const QPoint& pos, int* pt)
+bool spline_widget::is_on_pt(const QPointF& pos, int* pt)
{
- if (!_config)
+ if (!config)
{
if (pt)
*pt = -1;
return false;
}
- const points_t points = _config->get_points();
+ const points_t& points = config->get_points();
for (int i = 0; i < points.size(); i++)
{
@@ -668,3 +675,17 @@ bool spline_widget::is_on_pt(const QPoint& pos, int* pt)
*pt = -1;
return false;
}
+
+void spline_widget::changeEvent(QEvent* e)
+{
+ switch (e->type())
+ {
+ case QEvent::EnabledChange:
+ background_img = {}; spline_img = {};
+ update();
+ break;
+ default:
+ break;
+ }
+}
+} // ns spline_detail
diff --git a/spline/spline-widget.hpp b/spline/spline-widget.hpp
index 12d21970..610baf43 100644
--- a/spline/spline-widget.hpp
+++ b/spline/spline-widget.hpp
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012-2016 Stanislaw Halik <sthalik@misaki.pl>
+/* Copyright (c) 2012-2019 Stanislaw Halik <sthalik@misaki.pl>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -11,23 +11,23 @@
#include "spline.hpp"
#include "api/plugin-api.hpp"
+#include "compat/qt-dpi.hpp"
#include "options/options.hpp"
-using namespace options;
#include "export.hpp"
+#include <cmath>
+
#include <QWidget>
-#include <QRect>
-#include <QPoint>
-#include <QPointF>
-#include <QToolTip>
-#include <QShowEvent>
-#include <QFocusEvent>
#include <QMetaObject>
#include <QDebug>
-class OTR_SPLINE_EXPORT spline_widget final : public QWidget
+namespace spline_detail {
+
+using namespace options;
+
+class OTR_SPLINE_EXPORT spline_widget final : public QWidget, public screen_dpi_mixin<spline_widget>
{
Q_OBJECT
Q_PROPERTY(QColor colorBezier READ colorBezier WRITE setColorBezier)
@@ -35,27 +35,28 @@ class OTR_SPLINE_EXPORT spline_widget final : public QWidget
Q_PROPERTY(int x_step READ x_step WRITE set_x_step)
Q_PROPERTY(int y_step READ y_step WRITE set_y_step)
- using points_t = spline::points_t;
public:
- spline_widget(QWidget *parent = 0);
- ~spline_widget();
+ explicit spline_widget(QWidget *parent = nullptr);
+ ~spline_widget() override;
- void setConfig(base_spline* spl);
+ void set_config(base_spline* spl);
QColor colorBezier() const;
- void setColorBezier(QColor color);
+ void setColorBezier(QColor const& color);
void force_redraw();
void set_preview_only(bool val);
bool is_preview_only() const;
- double x_step() const { return _x_step; }
- double y_step() const { return _y_step; }
- void set_x_step(double val) { _x_step = std::fmax(1., val); }
- void set_y_step(double val) { _y_step = std::fmax(1., val); }
+ double x_step() const { return x_step_; }
+ double y_step() const { return y_step_; }
+ void set_x_step(double val) { x_step_ = std::fmax(1., val); }
+ void set_y_step(double val) { y_step_ = std::fmax(1., val); }
void set_snap(double x, double y) { snap_x = x; snap_y = y; }
void get_snap(double& x, double& y) const { x = snap_x; y = snap_y; }
+
+ QSize minimumSizeHint() const override;
public slots:
void reload_spline();
protected slots:
@@ -64,33 +65,35 @@ protected slots:
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
- int get_closeness_limit();
+ double min_pt_distance() const;
void show_tooltip(const QPoint& pos, const QPointF& value = QPointF(0, 0));
- bool is_in_bounds(const QPoint& pos) const;
+ bool is_in_bounds(const QPointF& pos) const;
void drawBackground();
void drawFunction();
void drawPoint(QPainter& painter, const QPointF& pt, const QColor& colBG, const QColor& border = QColor(50, 100, 120, 200));
- void drawLine(QPainter& painter, const QPoint& start, const QPoint& end, const QPen& pen);
- bool point_within_pixel(const QPointF& pt, const QPoint& pixel);
+ void drawLine(QPainter& painter, const QPointF& start, const QPointF& end, const QPen& pen);
+ bool point_within_pixel(const QPointF& pt, const QPointF& pixel);
void focusOutEvent(QFocusEvent*e) override;
void resizeEvent(QResizeEvent *) override;
- bool is_on_pt(const QPoint& pos, int* pt = nullptr);
+ bool is_on_pt(const QPointF& pos, int* pt = nullptr);
void update_range();
- QPointF pixel_coord_to_point(const QPoint& point);
+ void changeEvent(QEvent* e) override;
+
+ QPointF pixel_to_point(const QPointF& point);
+ QPointF point_to_pixel(const QPointF& point);
- QPointF point_to_pixel_(const QPointF& point);
- QPoint point_to_pixel(const QPointF& point);
+ static double snap(double x, double snap_value);
QPointF c;
- base_spline* _config = nullptr;
+ base_spline* config = nullptr;
- QPixmap _background;
- QPixmap _function;
+ QPixmap background_img;
+ QPixmap spline_img;
QColor spline_color;
- QColor widget_bg_color = palette().background().color();
+ QColor widget_bg_color = palette().window().color();
// bounds of the rectangle user can interact with
QRect pixel_bounds;
@@ -98,9 +101,16 @@ private:
QMetaObject::Connection connection;
double snap_x = 0, snap_y = 0;
- double _x_step = 10, _y_step = 10;
+ double x_step_ = 10, y_step_ = 10;
int moving_control_point_idx = -1;
- bool _draw_function = true, _preview_only = false;
+ bool draw_function = true, preview_only = false;
+
+ // point's circle radius on the widget
+ static constexpr int point_size_in_pixels_ = 5;
- static constexpr inline int point_size = 4;
+ const double point_size_in_pixels = point_size_in_pixels_ * screen_dpi();
};
+
+} // ns spline_detail
+
+using spline_widget = spline_detail::spline_widget;
diff --git a/spline/spline.cpp b/spline/spline.cpp
index fc77bf8b..466a9a7f 100644
--- a/spline/spline.cpp
+++ b/spline/spline.cpp
@@ -1,11 +1,3 @@
-/* Copyright (c) 2012-2016, Stanislaw Halik <sthalik@misaki.pl>
-
- * 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 "spline.hpp"
#include "compat/math.hpp"
@@ -18,200 +10,211 @@
#include <QObject>
#include <QMutexLocker>
-#include <QCoreApplication>
#include <QPointF>
#include <QSettings>
#include <QString>
#include <QDebug>
-using namespace spline_detail;
+namespace spline_detail {
+
+settings::~settings() = default;
+base_spline_::~base_spline_() = default;
+base_spline::~base_spline() = default;
+spline_modify_mixin::~spline_modify_mixin() = default;
+spline_settings_mixin::~spline_settings_mixin() = default;
-spline::spline(const QString& name, const QString& axis_name, Axis axis) :
- axis(axis)
+spline::spline(const QString& name, const QString& axis_name, Axis axis)
{
set_bundle(options::make_bundle(name), axis_name, axis);
}
spline::~spline()
{
- QMutexLocker l(&_mutex);
-
- if (connection)
- {
- QObject::disconnect(connection);
- QObject::disconnect(conn_maxx);
- QObject::disconnect(conn_maxy);
- connection = QMetaObject::Connection();
- conn_maxx = QMetaObject::Connection();
- conn_maxy = QMetaObject::Connection();
- }
+ QMutexLocker l(&mtx);
+ disconnect_signals();
}
-spline::spline() : spline("", "", Axis(-1)) {}
+spline::spline() : spline(QString{}, QString{}, Axis(-1)) {}
-void spline::set_tracking_active(bool value)
+void spline::set_tracking_active(bool value) const
{
- QMutexLocker l(&_mutex);
- activep = value;
+ std::shared_ptr<settings> S;
+ {
+ QMutexLocker l(&mtx);
+ if (value == activep)
+ return;
+ activep = value;
+ S = s;
+ }
+ emit S->recomputed();
}
bundle spline::get_bundle()
{
- QMutexLocker l(&_mutex); // avoid logic errors due to changes in value<t> data
+ QMutexLocker l(&mtx);
return s->b;
}
void spline::clear()
{
- QMutexLocker l(&_mutex);
- s->points = points_t();
- validp = false;
+ std::shared_ptr<settings> S;
+ {
+ QMutexLocker l(&mtx);
+ S = s;
+ s->points = {};
+ points = {};
+ update_interp_data();
+ }
+ emit S->recomputed();
}
-float spline::get_value(double x)
+double spline::get_value(double x) const
{
- QMutexLocker foo(&_mutex);
+ QMutexLocker l(&mtx);
- const float ret = get_value_no_save(x);
- last_input_value.setX(std::fabs(x));
- last_input_value.setY(double(std::fabs(ret)));
+ const double ret = get_value_no_save(x);
+ last_input_value = { std::fabs(x), std::fabs((double)ret) };
return ret;
}
-float spline::get_value_no_save(double x) const
-{
- return const_cast<spline&>(*this).get_value_no_save_internal(x);
-}
-
-float spline::get_value_no_save_internal(double x)
+double spline::get_value_no_save(double x) const
{
- QMutexLocker foo(&_mutex);
+ QMutexLocker l(&mtx);
- float q = float(x * bucket_size_coefficient(s->points));
+ double q = x * bucket_size_coefficient(points);
int xi = (int)q;
- float yi = get_value_internal(xi);
- float yiplus1 = get_value_internal(xi+1);
- float f = (q-xi);
- float ret = yiplus1 * f + yi * (1.0f - f); // at least do a linear interpolation.
+ double yi = get_value_internal(xi);
+ double yiplus1 = get_value_internal(xi+1);
+ double f = (q-xi);
+ double ret = yiplus1 * f + yi * (1 - f); // at least do a linear interpolation.
return ret;
}
-warn_result_unused bool spline::get_last_value(QPointF& point)
+bool spline::get_last_value(QPointF& point)
{
- QMutexLocker foo(&_mutex);
+ QMutexLocker foo(&mtx);
point = last_input_value;
- return activep;
+ return activep && point.y() >= 0;
}
-float spline::get_value_internal(int x)
+double spline::get_value_internal(int x) const
{
- if (!validp)
- {
- update_interp_data();
- validp = true;
- }
-
- const float sign = signum(x);
+ const auto sign = (f)signum(x);
x = std::abs(x);
- const float ret_ = data[std::min(unsigned(x), unsigned(value_count)-1u)];
- return sign * clamp(ret_, 0, 1000);
-}
-
-void spline::add_lone_point()
-{
- points_t points;
- points.push_back(QPointF(s->opts.clamp_x_, s->opts.clamp_y_));
-
- s->points = points;
+ const auto ret_ = data[std::min(unsigned(x), value_count - 1)];
+ return (double)(sign * std::clamp(ret_, (f)0, (f)1000));
}
-QPointF spline::ensure_in_bounds(const QList<QPointF>& points, int i)
+void spline::ensure_in_bounds(const QList<QPointF>& points, int i, f& x, f& y)
{
const int sz = points.size();
if (i < 0 || sz == 0)
- return QPointF(0, 0);
-
- if (i < sz)
- return points[i];
-
- return points[sz - 1];
+ {
+ x = 0;
+ y = 0;
+ }
+ else if (i < sz)
+ {
+ const QPointF& pt = points[i];
+ x = (f)pt.x();
+ y = (f)pt.y();
+ }
+ else
+ {
+ const QPointF& pt = points[sz - 1];
+ x = (f)pt.x();
+ y = (f)pt.y();
+ }
}
int spline::element_count(const QList<QPointF>& points, double max_input)
{
- const unsigned sz = points.size();
- for (unsigned k = 0; k < sz; k++)
+ const int sz = points.size();
+ for (int k = sz-1; k >= 0; k--)
{
const QPointF& pt = points[k];
- if (max_input > 1e-4 && pt.x() - 1e-2 > max_input)
+ if (max_input > 1e-4 && pt.x() - 1e-4 >= max_input)
return k;
}
return sz;
}
-bool spline::sort_fn(const QPointF& one, const QPointF& two)
+bool spline::sort_fn(QPointF one, QPointF two)
{
return one.x() < two.x();
}
-void spline::update_interp_data()
+void spline::update_interp_data() const
{
- points_t points = s->points;
- ensure_valid(points);
- const int sz = points.size();
+ points_t list = points;
+ ensure_valid(list);
+ int sz = list.size();
- const double maxx = max_input();
-
- if (sz == 0)
- points.prepend(QPointF(maxx, max_output()));
+ if (list.isEmpty())
+ list.prepend({ max_input(), max_output() });
- std::stable_sort(points.begin(), points.begin() + sz, sort_fn);
-
- const double c = bucket_size_coefficient(points);
- const double c_interp = c * 30;
+ const double c = bucket_size_coefficient(list);
+ const double c_ = c * c_interp;
+ const f cf = (f)c, c_f = (f)c_;
for (unsigned i = 0; i < value_count; i++)
- data[i] = -16;
+ data[i] = magic_fill_value;
- if (sz < 2)
+ if (sz < 2) // lerp only
{
- if (points[0].x() - 1e-2 < maxx)
+ const QPointF& pt = list[0];
+ const double x = pt.x();
+ const double y = pt.y();
+ const unsigned max = std::clamp((unsigned)iround(x * c), 1u, value_count-1);
+
+ for (unsigned k = 0; k <= max; k++)
+ data[k] = f(y * k / max); // no need for bresenham
+ }
+ else if (sz == 2 && list[0].y() < 1e-6)
+ {
+ unsigned start = std::clamp((unsigned)iround(list[0].x() * c), 1u, value_count-1);
+ unsigned end = std::clamp((unsigned)iround(list[1].x() * c), 2u, value_count-1);
+ unsigned max = end - start;
+ for (unsigned x = 0; x < start; x++)
+ data[x] = 0;
+ for (unsigned x = 0; x < max; x++)
+ data[start + x] = (f)(list[1].y() * x / max);
+ }
+ else
+ {
+ if (list[0].x() > 1e-6)
{
- const double x = points[0].x();
- const double y = points[0].y();
- const unsigned max = (unsigned)clamp(iround(x * c), 1, value_count-1);
- for (unsigned k = 0; k <= max; k++)
+ double zero_pos = 0;
+ while (list.size() > 1 && list[0].y() <= 1e-6)
{
- if (k < value_count)
- data[unsigned(k)] = float(y * k / max);
+ zero_pos = list[0].x();
+ list.pop_front();
}
+ list.push_front({zero_pos, 0});
+ sz = list.size();
}
- }
- else
- {
- if (points[0].x() > 1e-2 && points[0].x() <= maxx)
- points.push_front(QPointF(0, 0));
+ // now this is hella expensive due to `c_interp'
for (int i = 0; i < sz; i++)
{
- const QPointF p0 = ensure_in_bounds(points, i - 1);
- const QPointF p1 = ensure_in_bounds(points, i + 0);
- const QPointF p2 = ensure_in_bounds(points, i + 1);
- const QPointF p3 = ensure_in_bounds(points, i + 2);
- const double p0_x = p0.x(), p1_x = p1.x(), p2_x = p2.x(), p3_x = p3.x();
- const double p0_y = p0.y(), p1_y = p1.y(), p2_y = p2.y(), p3_y = p3.y();
-
- const double cx[4] = {
+ f p0_x, p1_x, p2_x, p3_x;
+ f p0_y, p1_y, p2_y, p3_y;
+
+ ensure_in_bounds(list, i - 1, p0_x, p0_y);
+ ensure_in_bounds(list, i + 0, p1_x, p1_y);
+ ensure_in_bounds(list, i + 1, p2_x, p2_y);
+ ensure_in_bounds(list, i + 2, p3_x, p3_y);
+
+ const f cx[4] = {
2 * p1_x, // 1
-p0_x + p2_x, // t
2 * p0_x - 5 * p1_x + 4 * p2_x - p3_x, // t^2
-p0_x + 3 * p1_x - 3 * p2_x + p3_x, // t3
};
- const double cy[4] =
- {
+ const f cy[4] = {
2 * p1_y, // 1
-p0_y + p2_y, // t
2 * p0_y - 5 * p1_y + 4 * p2_y - p3_y, // t^2
@@ -219,275 +222,315 @@ void spline::update_interp_data()
};
// multiplier helps fill in all the x's needed
- const unsigned end = int(c_interp * (p2_x - p1_x)) + 1;
+ const unsigned end = (unsigned)(c_f * (p2_x - p1_x)) + 1;
+ const f end_(end);
for (unsigned k = 0; k <= end; k++)
{
- const double t = k / double(end);
- const double t2 = t*t;
- const double t3 = t*t*t;
-
- const int x = int(.5 * c * (cx[0] + cx[1] * t + cx[2] * t2 + cx[3] * t3));
- const float y = float(.5 * (cy[0] + cy[1] * t + cy[2] * t2 + cy[3] * t3));
-
- if (unsigned(x) < value_count)
- data[x] = y;
+ const f t = k / end_;
+ const f t2 = t*t;
+ const f t3 = t*t*t;
+
+ const auto x = (unsigned)(f(.5) * cf * (cx[0] + cx[1] * t + cx[2] * t2 + cx[3] * t3));
+ const auto y = (f)(f(.5) * (cy[0] + cy[1] * t + cy[2] * t2 + cy[3] * t3));
+
+ switch (int ret = std::fpclassify(y))
+ {
+ case FP_INFINITE:
+ case FP_NAN:
+ case FP_SUBNORMAL:
+ eval_once(qDebug() << "spline: fpclassify" << y
+ << "returned" << ret
+ << "for bundle" << s->b->name());
+ continue;
+ case FP_ZERO:
+ case FP_NORMAL:
+ if (x < value_count)
+ data[x] = y;
+ break;
+ default:
+ unreachable();
+ }
}
}
}
- float last = 0;
- for (unsigned i = 0; i < unsigned(value_count); i++)
+ auto maxy = (f)max_output();
+ auto last = (f)0;
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wfloat-equal" // stupid clang
+#endif
+
+ for (unsigned i = 0; i < value_count; i++)
{
- if (data[i] == -16)
+ if (data[i] == magic_fill_value)
data[i] = last;
+ data[i] = std::clamp(data[i], (f)0, (f)maxy);
last = data[i];
}
+
+ // make sure empty places stay empty (see #1341)
+ if (auto it = std::find_if(list.cbegin(), list.cend(),
+ [](QPointF x) { return x.x() >= (f)1e-6 && x.y() >= (f)1e-6; });
+ it != list.cend() && it != list.cbegin())
+ {
+ it--;
+ unsigned max = std::clamp((unsigned)iround(it->x() * c), 0u, value_count-1);
+
+ for (unsigned x = 0; x < max; x++)
+ data[x] = 0;
+ }
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
}
void spline::remove_point(int i)
{
- QMutexLocker foo(&_mutex);
+ std::shared_ptr<settings> S;
+ {
+ QMutexLocker foo(&mtx);
+ S = s;
- points_t points = s->points;
- const int sz = element_count(points, max_input());
+ const int sz = element_count(points, max_input());
- if (i >= 0 && i < sz)
- {
- points.erase(points.begin() + i);
- s->points = points;
- validp = false;
+ if (i >= 0 && i < sz)
+ {
+ points.erase(points.begin() + i);
+ s->points = points;
+ update_interp_data();
+ }
}
+
+ emit S->recomputed();
}
void spline::add_point(QPointF pt)
{
- QMutexLocker foo(&_mutex);
-
- points_t points = s->points;
- points.push_back(pt);
- std::stable_sort(points.begin(), points.end(), sort_fn);
- s->points = points;
- validp = false;
+ std::shared_ptr<settings> S;
+ {
+ QMutexLocker foo(&mtx);
+ S = s;
+ points.push_back(pt);
+ std::stable_sort(points.begin(), points.end(), sort_fn);
+ s->points = points;
+ update_interp_data();
+ }
+ emit S->recomputed();
}
void spline::add_point(double x, double y)
{
- add_point(QPointF(x, y));
+ add_point({ x, y });
}
void spline::move_point(int idx, QPointF pt)
{
- QMutexLocker foo(&_mutex);
-
- points_t points = s->points;
+ std::shared_ptr<settings> S;
+ {
+ QMutexLocker foo(&mtx);
+ S = s;
- const int sz = element_count(points, max_input());
+ const int sz = element_count(points, max_input());
- if (idx >= 0 && idx < sz)
- {
- points[idx] = pt;
- // we don't allow points to be reordered, but sort due to possible caller logic error
- std::stable_sort(points.begin(), points.end(), sort_fn);
- s->points = points;
- validp = false;
+ if (idx >= 0 && idx < sz)
+ {
+ points[idx] = pt;
+ std::stable_sort(points.begin(), points.end(), sort_fn);
+ s->points = points;
+ update_interp_data();
+ }
}
+ emit S->recomputed();
}
-spline::points_t spline::get_points() const
+const points_t& spline::get_points() const
{
- QMutexLocker foo(&_mutex);
- return s->points;
+ return points;
}
int spline::get_point_count() const
{
- QMutexLocker foo(&_mutex);
- return element_count(s->points, s->opts.clamp_x_);
+ QMutexLocker foo(&mtx);
+ return element_count(points, clamp_x);
}
void spline::reload()
{
- QMutexLocker foo(&_mutex);
+ QMutexLocker foo(&mtx);
s->b->reload();
}
void spline::save()
{
- QMutexLocker foo(&_mutex);
+ QMutexLocker foo(&mtx);
s->b->save();
}
-void spline::invalidate_settings()
+void spline::invalidate_settings_()
{
- // we're holding the mutex to allow signal disconnection in spline dtor
- // before this slot gets called for the next time
-
- QMutexLocker l(&_mutex);
- validp = false;
+ points = s->points;
+ clamp_x = s->opts.clamp_x_;
+ clamp_y = s->opts.clamp_y_;
+ update_interp_data();
+}
- emit s->recomputed();
+void spline::invalidate_settings()
+{
+ std::shared_ptr<settings> S;
+ {
+ QMutexLocker l(&mtx);
+ S = s;
+ invalidate_settings_();
+ }
+ emit S->recomputed();
}
void spline::set_bundle(bundle b, const QString& axis_name, Axis axis)
{
- QMutexLocker l(&_mutex);
+ if (!b)
+ b = make_bundle({});
- // gets called from ctor hence the need for nullptr checks
- // the sentinel settings/bundle objects don't need any further branching once created
- if (!s || s->b != b)
- {
- s = std::make_shared<settings>(b, axis_name, axis);
+ std::shared_ptr<settings> S;
- if (connection)
- {
- QObject::disconnect(connection);
- QObject::disconnect(conn_maxx);
- QObject::disconnect(conn_maxy);
- }
-
- if (b)
- {
- connection = QObject::connect(b.get(), &bundle_::changed,
- s.get(), [&]() { invalidate_settings(); });
-
- // this isn't strictly necessary for the spline but helps the widget
- conn_maxx = QObject::connect(&s->opts.clamp_x_, base_value::value_changed<int>(),
- ctx.get(), [&](double) { invalidate_settings(); });
- conn_maxy = QObject::connect(&s->opts.clamp_y_, base_value::value_changed<int>(),
- ctx.get(), [&](double) { invalidate_settings(); });
- }
+ {
+ QMutexLocker l(&mtx);
- validp = false;
+ disconnect_signals();
+ s = std::make_shared<settings>(b, axis_name, axis);
+ invalidate_settings_();
+ S = s;
+
+ conn_points = QObject::connect(&s->points, value_::value_changed<QList<QPointF>>(),
+ &*ctx, [this] { invalidate_settings(); }, Qt::DirectConnection);
+ conn_maxx = QObject::connect(&s->opts.clamp_x_, value_::value_changed<int>(),
+ &*ctx, [this](double) { invalidate_settings(); }, Qt::DirectConnection);
+ conn_maxy = QObject::connect(&s->opts.clamp_y_, value_::value_changed<int>(),
+ &*ctx, [this](double) { invalidate_settings(); }, Qt::DirectConnection);
}
+
+ emit S->recomputed();
}
double spline::max_input() const
{
- QMutexLocker l(&_mutex);
- if (s)
- {
- using m = axis_opts::max_clamp;
- const value<m>& clamp = s->opts.clamp_x_;
- const QList<QPointF> points = s->points;
- if (clamp == m::x1000 && points.size())
- return points[points.size() - 1].x();
- return s ? std::fabs(clamp.to<double>()) : 0;
- }
- return 0;
+ QMutexLocker l(&mtx);
+ if (clamp_x == axis_opts::x1000)
+ return std::fmax(1, points.empty() ? 0 : points[points.size() - 1].x());
+ return std::fabs((double)clamp_x);
}
double spline::max_output() const
{
- QMutexLocker l(&_mutex);
- if (s)
- {
- using m = axis_opts::max_clamp;
- const value<m>& clamp = s->opts.clamp_y_;
- const QList<QPointF> points = s->points;
- if (clamp == m::x1000 && points.size())
- return points[points.size() - 1].y();
- return s ? std::fabs(clamp.to<double>()) : 0;
- }
- return 0;
+ QMutexLocker l(&mtx);
+ if (clamp_y == axis_opts::x1000 && !points.empty())
+ return std::fmax(1, points.empty() ? 0 : points[points.size() - 1].y());
+ return std::fabs((double)clamp_y);
}
-void spline::ensure_valid(QList<QPointF>& the_points)
+void spline::ensure_valid(points_t& list) const
{
- QMutexLocker foo(&_mutex);
-
- QList<QPointF> list = the_points;
-
- // storing to s->points fires bundle::changed and that leads to an infinite loop
- // thus, only store if we can't help it
std::stable_sort(list.begin(), list.end(), sort_fn);
- const int sz = list.size();
+ const unsigned sz = (unsigned)list.size();
- QList<QPointF> ret_list, ret_list_2;
- ret_list.reserve(sz), ret_list_2.reserve(sz);
+ QList<QPointF> tmp_points, all_points;
+ tmp_points.reserve(sz); all_points.reserve(sz);
- const double maxx = max_input(), maxy = max_output();
+ const double maxx = max_input();
- for (int i = 0; i < sz; i++)
+ for (unsigned i = 0; i < sz; i++)
{
- QPointF& pt(list[i]);
+ QPointF& pt{list[i]};
- const bool overlap = progn(
- for (int j = 0; j < i; j++)
+ bool overlap = false;
+ for (unsigned j = i+1; j < sz; j++)
+ {
+ const QPointF& pt2{list[j]};
+ const QPointF tmp(pt - pt2);
+ const double dist_sq = QPointF::dotProduct(tmp, tmp);
+ constexpr double min_dist = 1e-4;
+ if (dist_sq < min_dist)
{
- const QPointF& pt2(list[j]);
- const QPointF tmp(pt - pt2);
- const double dist_sq = QPointF::dotProduct(tmp, tmp);
- const double overlap = maxx / 500.;
- if (dist_sq < overlap * overlap)
- return true;
+ overlap = true;
+ break;
}
- return false;
- );
+ }
if (!overlap)
- ret_list_2.append(pt);
-
- if (pt.x() - 1e-2 < maxx && pt.x() >= 0 &&
- pt.y() - 1e-2 < maxy && pt.y() >= 0 && !overlap)
{
- ret_list.push_back(pt);
+ all_points.append(pt); // all points total
+
+ // points within selected limit, for use in `update_interp_data'
+ if (pt.x() - 1e-4 <= maxx && pt.x() >= 0)
+ tmp_points.push_back(pt);
}
}
- if (ret_list != the_points)
+ // simply storing to s->points fires bundle::changed leading to a livelock
+ // hence only store if we can't help it
+
+ if (all_points.size() < points.size())
{
- s->points = std::move(ret_list_2);
- the_points = std::move(ret_list);
+ // all points that don't overlap
+ points = std::move(all_points);
+ s->points = points;
}
- last_input_value = QPointF(0, 0);
+ if (tmp_points.size() < list.size())
+ // points that are within currently-specified bounds
+ list = std::move(tmp_points);
+
+ last_input_value = {};
activep = false;
}
-// the return value is only safe to use with no spline::set_bundle calls
std::shared_ptr<base_settings> spline::get_settings()
{
- QMutexLocker foo(&_mutex);
+ QMutexLocker foo(&mtx);
return std::static_pointer_cast<base_settings>(s);
}
std::shared_ptr<const base_settings> spline::get_settings() const
{
- QMutexLocker foo(&_mutex);
+ QMutexLocker foo(&mtx);
return std::static_pointer_cast<const base_settings>(s);
}
double spline::bucket_size_coefficient(const QList<QPointF>& points) const
{
constexpr double eps = 1e-4;
-
const double maxx = max_input();
if (maxx < eps)
return 0;
- // needed to fill the buckets up to the last control point.
- // space between that point and max_x doesn't matter.
+ // buckets are only used between 0 and the rightmost point's
+ // x coordinate. even if `clamp_x' is set to a much larger value,
+ // buckets retain more precision lower the x coordinate of the
+ // last point.
const int sz = element_count(points, maxx);
const double last_x = sz ? points[sz - 1].x() : maxx;
- return clamp((value_count-1) / clamp(last_x, eps, maxx), 0., (value_count-1));
+ return std::clamp((value_count-1) / std::clamp(last_x, eps, maxx), 0., (value_count-1.));
}
-namespace spline_detail {
-
-settings::settings(bundle b, const QString& axis_name, Axis idx):
- b(b ? b : make_bundle("")),
- points(b, "points", {}),
- opts(axis_name, idx)
-{}
-
-settings::~settings()
+void spline::disconnect_signals()
{
+ if (conn_points)
+ {
+ QObject::disconnect(conn_points); conn_points = {};
+ QObject::disconnect(conn_maxx); conn_maxx = {};
+ QObject::disconnect(conn_maxy); conn_maxy = {};
+ }
}
-}
+settings::settings(bundle const& b, const QString& axis_name, Axis idx):
+ b(b ? b : make_bundle({})),
+ opts(axis_name, idx)
+{}
+} // ns spline_detail
diff --git a/spline/spline.hpp b/spline/spline.hpp
index a3532855..780442b9 100644
--- a/spline/spline.hpp
+++ b/spline/spline.hpp
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012-2016, Stanislaw Halik <sthalik@misaki.pl>
+/* Copyright (c) 2012-2019, Stanislaw Halik <sthalik@misaki.pl>
* Permission to use, copy, modify, and/or distribute this
* software for any purpose with or without fee is hereby granted,
@@ -8,19 +8,15 @@
#pragma once
-#include "compat/copyable-mutex.hpp"
#include "options/options.hpp"
-using namespace options;
-
#include "axis-opts.hpp"
-
#include "export.hpp"
+#include "compat/mutex.hpp"
#include <cstddef>
#include <vector>
#include <limits>
#include <memory>
-#include <functional>
#include <QObject>
#include <QPointF>
@@ -29,6 +25,9 @@ using namespace options;
namespace spline_detail {
+using points_t = QList<QPointF>;
+using namespace options;
+
class OTR_SPLINE_EXPORT base_settings : public QObject
{
Q_OBJECT
@@ -41,34 +40,43 @@ class OTR_SPLINE_EXPORT settings final : public base_settings
{
public:
bundle b;
- value<QList<QPointF>> points;
+ value<QList<QPointF>> points { b, "points", {} };
axis_opts opts;
- settings(bundle b, const QString& axis_name, Axis idx);
+ settings(bundle const& b, const QString& axis_name, Axis idx);
~settings() override;
};
-} // ns spline_detail
-
struct OTR_SPLINE_EXPORT base_spline_
{
- virtual inline ~base_spline_();
+ base_spline_() = default;
+ virtual ~base_spline_();
- virtual float get_value(double x) = 0;
- virtual float get_value_no_save(double x) const = 0;
+ virtual double get_value(double x) const = 0;
+ virtual double get_value_no_save(double x) const = 0;
- warn_result_unused virtual bool get_last_value(QPointF& point) = 0;
- virtual void set_tracking_active(bool value) = 0;
+ [[nodiscard]] virtual bool get_last_value(QPointF& point) = 0;
+ virtual void set_tracking_active(bool value) const = 0;
virtual double max_input() const = 0;
virtual double max_output() const = 0;
- using points_t = QList<QPointF>;
-
- virtual points_t get_points() const = 0;
+ virtual const points_t& get_points() const = 0;
virtual int get_point_count() const = 0;
- virtual std::shared_ptr<spline_detail::base_settings> get_settings() = 0;
- virtual std::shared_ptr<const spline_detail::base_settings> get_settings() const = 0;
+ base_spline_(const base_spline_&) = default;
+ base_spline_& operator=(const base_spline_&) = default;
+};
+
+struct OTR_SPLINE_EXPORT spline_settings_mixin
+{
+ virtual std::shared_ptr<base_settings> get_settings() = 0;
+ virtual std::shared_ptr<const base_settings> get_settings() const = 0;
+
+ spline_settings_mixin(const spline_settings_mixin&) = default;
+ spline_settings_mixin& operator=(const spline_settings_mixin&) = default;
+
+ spline_settings_mixin() = default;
+ virtual ~spline_settings_mixin();
};
struct OTR_SPLINE_EXPORT spline_modify_mixin
@@ -79,42 +87,52 @@ struct OTR_SPLINE_EXPORT spline_modify_mixin
virtual void remove_point(int i) = 0;
virtual void clear() = 0;
- virtual inline ~spline_modify_mixin();
+ spline_modify_mixin(const spline_modify_mixin&) = default;
+ spline_modify_mixin& operator=(const spline_modify_mixin&) = default;
+
+ spline_modify_mixin() = default;
+ virtual ~spline_modify_mixin();
};
-class OTR_SPLINE_EXPORT base_spline : public base_spline_, public spline_modify_mixin
+struct OTR_SPLINE_EXPORT base_spline : base_spline_, spline_modify_mixin, spline_settings_mixin
{
-public:
- using base_settings = spline_detail::base_settings;
+ base_spline(const base_spline&) = default;
+ base_spline& operator=(const base_spline&) = default;
+
+ base_spline() = default;
+ ~base_spline() override;
};
class OTR_SPLINE_EXPORT spline : public base_spline
{
+ using f = double;
+
double bucket_size_coefficient(const QList<QPointF>& points) const;
- void update_interp_data();
- float get_value_internal(int x);
- void add_lone_point();
- float get_value_no_save_internal(double x);
- static bool sort_fn(const QPointF& one, const QPointF& two);
+ void update_interp_data() const;
+ double get_value_internal(int x) const;
+ static bool sort_fn(QPointF one, QPointF two);
- static QPointF ensure_in_bounds(const QList<QPointF>& points, int i);
+ static void ensure_in_bounds(const QList<QPointF>& points, int i, f& x, f& y);
static int element_count(const QList<QPointF>& points, double max_input);
- std::shared_ptr<spline_detail::settings> s;
- QMetaObject::Connection connection, conn_maxx, conn_maxy;
-
- static constexpr inline std::size_t value_count = 4096;
+ void disconnect_signals();
+ void invalidate_settings_();
- std::vector<float> data = std::vector<float>(value_count, float(-16));
+ mutex mtx { mutex::Recursive };
+ std::shared_ptr<settings> s;
+ QMetaObject::Connection conn_points, conn_maxx, conn_maxy;
- mutex _mutex { mutex::recursive };
- QPointF last_input_value;
std::shared_ptr<QObject> ctx { std::make_shared<QObject>() };
- Axis axis = NonAxis;
+ mutable QPointF last_input_value{-1, -1};
+ mutable std::vector<f> data = std::vector<f>(value_count, magic_fill_value);
+ mutable points_t points;
+ mutable axis_opts::max_clamp clamp_x = axis_opts::x1000, clamp_y = axis_opts::x1000;
+ mutable bool activep = false;
- bool activep = false;
- bool validp = false;
+ static constexpr unsigned value_count = 8192;
+ static constexpr f magic_fill_value = -(1 << 24) + 1;
+ static constexpr double c_interp = 5;
public:
void invalidate_settings();
@@ -128,14 +146,13 @@ public:
spline();
spline(const QString& name, const QString& axis_name, Axis axis);
- ~spline();
+ ~spline() override;
- spline& operator=(const spline&) = default;
spline(const spline&) = default;
- float get_value(double x) override;
- float get_value_no_save(double x) const override;
- warn_result_unused bool get_last_value(QPointF& point) override;
+ double get_value(double x) const override;
+ double get_value_no_save(double x) const override;
+ [[nodiscard]] bool get_last_value(QPointF& point) override;
void add_point(QPointF pt) override;
void add_point(double x, double y) override;
@@ -143,19 +160,18 @@ public:
void remove_point(int i) override;
void clear() override;
- points_t get_points() const override;
+ const points_t& get_points() const override;
- void set_tracking_active(bool value) override;
+ void set_tracking_active(bool value) const override;
bundle get_bundle();
- void ensure_valid(QList<QPointF>& the_points);
+ void ensure_valid(points_t& in_out) const;
- std::shared_ptr<spline_detail::base_settings> get_settings() override;
- std::shared_ptr<const spline_detail::base_settings> get_settings() const override;
+ std::shared_ptr<base_settings> get_settings() override;
+ std::shared_ptr<const base_settings> get_settings() const override;
int get_point_count() const override;
-
- using settings = spline_detail::settings;
};
-inline base_spline_::~base_spline_() {}
-inline spline_modify_mixin::~spline_modify_mixin() {}
+} // ns spline_detail
+
+using spline = spline_detail::spline;
diff --git a/tracker-aruco/CMakeLists.txt b/tracker-aruco/CMakeLists.txt
index c861ae17..333edb3e 100644
--- a/tracker-aruco/CMakeLists.txt
+++ b/tracker-aruco/CMakeLists.txt
@@ -1,10 +1,47 @@
-find_package(OpenCV 3.0 QUIET)
+function(maybe_add_static_define)
+ get_filename_component(aruco-ext "${SDK_ARUCO_LIBPATH}" EXT)
+ string(TOLOWER "${aruco-ext}" aruco-ext)
+ if(aruco-ext STREQUAL ".lib" OR aruco-ext STREQUAL ".a")
+ add_definitions(-DARUCO_STATIC)
+ endif()
+endfunction()
+
+
+include(opentrack-opencv)
+find_package(OpenCV QUIET)
+
if(OpenCV_FOUND)
+ set(opencv-modules core calib3d imgproc features2d flann)
+ foreach(k ${opencv-modules})
+ if(NOT TARGET "opencv_${k}")
+ return()
+ endif()
+ endforeach()
+ foreach(k ${opencv-modules})
+ otr_install_lib("opencv_${k}" "${opentrack-libexec}")
+ endforeach()
set(SDK_ARUCO_LIBPATH "" CACHE FILEPATH "Aruco paper marker tracker static library path")
if(SDK_ARUCO_LIBPATH)
+ set(modules "${SDK_ARUCO_LIBPATH}" opencv_calib3d opencv_imgproc opencv_core)
+
+ get_filename_component(dir "${SDK_ARUCO_LIBPATH}" DIRECTORY)
+ get_filename_component(dir "${dir}" ABSOLUTE)
+ set(dir "${dir}/include")
+
+ try_compile(tracker-aruco_has-working-abi "${CMAKE_CURRENT_BINARY_DIR}"
+ SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/compile-test/abi.cpp"
+ CMAKE_FLAGS "-DINCLUDE_DIRECTORIES:STRING=${dir}"
+ "-DCXX_STANDARD=20" "-DCXX_STANDARD_REQUIRED=1"
+ OUTPUT_VARIABLE krap)
+ if(NOT tracker-aruco_has-working-abi)
+ message(FATAL_ERROR "${krap}\n" "Must use Aruco fork from <https://github.com/opentrack/aruco>")
+ endif()
+
+ maybe_add_static_define()
+ otr_install_lib("${SDK_ARUCO_LIBPATH}" "${opentrack-libexec}")
+
otr_module(tracker-aruco)
- set(modules opencv_core opencv_calib3d opencv_imgproc opencv_videoio)
- target_link_libraries(opentrack-tracker-aruco opentrack-cv ${SDK_ARUCO_LIBPATH} ${modules})
- target_include_directories(opentrack-tracker-aruco SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS})
+ target_include_directories(${self} SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS} "${dir}")
+ target_link_libraries(${self} opentrack-cv ${modules})
endif()
endif()
diff --git a/tracker-aruco/aruco-trackercontrols.ui b/tracker-aruco/aruco-trackercontrols.ui
index 0d70d9ed..729d4b48 100644
--- a/tracker-aruco/aruco-trackercontrols.ui
+++ b/tracker-aruco/aruco-trackercontrols.ui
@@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>453</width>
- <height>246</height>
+ <width>457</width>
+ <height>230</height>
</rect>
</property>
<property name="windowTitle">
@@ -64,50 +64,58 @@
<item>
<widget class="QFrame" name="frame">
<layout class="QGridLayout" name="gridLayout_4">
- <item row="0" column="0">
- <widget class="QLabel" name="label_9">
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_11">
<property name="text">
- <string>Diagonal FOV</string>
+ <string>Resolution</string>
</property>
</widget>
</item>
- <item row="2" column="1">
- <widget class="QComboBox" name="cameraName">
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>Camera name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="cameraFOV">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
+ <property name="locale">
+ <locale language="English" country="UnitedStates"/>
+ </property>
+ <property name="minimum">
+ <number>35</number>
+ </property>
+ <property name="maximum">
+ <number>90</number>
+ </property>
</widget>
</item>
- <item row="3" column="1">
- <widget class="QComboBox" name="resolution">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_12">
+ <property name="text">
+ <string>Frames per second</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="cameraFPS">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <item>
- <property name="text">
- <string>640x480</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>320x240</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Default (not recommended!)</string>
- </property>
- </item>
</widget>
</item>
- <item row="1" column="1">
- <widget class="QComboBox" name="cameraFPS">
+ <item row="4" column="1">
+ <widget class="QComboBox" name="resolution">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -116,74 +124,46 @@
</property>
<item>
<property name="text">
- <string>Default</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>30</string>
+ <string>640x480</string>
</property>
</item>
<item>
<property name="text">
- <string>60</string>
+ <string>320x240</string>
</property>
</item>
<item>
<property name="text">
- <string>75</string>
+ <string>1280x720</string>
</property>
</item>
<item>
<property name="text">
- <string>125</string>
+ <string>1920x1080</string>
</property>
</item>
<item>
<property name="text">
- <string>200</string>
+ <string>Default (not recommended!)</string>
</property>
</item>
</widget>
</item>
- <item row="0" column="1">
- <widget class="QSpinBox" name="cameraFOV">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>Diagonal FOV</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="cameraName">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="locale">
- <locale language="English" country="UnitedStates"/>
- </property>
- <property name="minimum">
- <number>35</number>
- </property>
- <property name="maximum">
- <number>90</number>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_10">
- <property name="text">
- <string>Camera name</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_12">
- <property name="text">
- <string>Frames per second</string>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_11">
- <property name="text">
- <string>Resolution</string>
- </property>
</widget>
</item>
<item row="5" column="1">
@@ -199,21 +179,24 @@
</property>
</widget>
</item>
- <item row="4" column="0">
+ <item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
- <string>Model rotation</string>
+ <string>MJPEG</string>
</property>
</widget>
</item>
- <item row="4" column="1">
- <widget class="QComboBox" name="model_rotation">
+ <item row="3" column="1">
+ <widget class="QCheckBox" name="use_mjpeg">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
+ <property name="text">
+ <string/>
+ </property>
</widget>
</item>
</layout>
diff --git a/tracker-aruco/compile-test/abi.cpp b/tracker-aruco/compile-test/abi.cpp
new file mode 100644
index 00000000..9c72fe2a
--- /dev/null
+++ b/tracker-aruco/compile-test/abi.cpp
@@ -0,0 +1,7 @@
+#include <aruco/abi.h>
+
+#if ARUCO_OPENTRACK_FORK_ABI != 2
+# error "wrong ABI"
+#endif
+
+int main(void) { return 0; }
diff --git a/tracker-aruco/ftnoir_tracker_aruco.cpp b/tracker-aruco/ftnoir_tracker_aruco.cpp
index faa8bb44..5130a889 100644
--- a/tracker-aruco/ftnoir_tracker_aruco.cpp
+++ b/tracker-aruco/ftnoir_tracker_aruco.cpp
@@ -5,12 +5,10 @@
* copyright notice and this permission notice appear in all copies.
*/
-#include "cv/video-widget.hpp"
#include "ftnoir_tracker_aruco.h"
-#include "cv/video-property-page.hpp"
-#include "compat/camera-names.hpp"
#include "compat/sleep.hpp"
#include "compat/math-imports.hpp"
+#include "cv/init.hpp"
#ifdef _MSC_VER
# pragma warning(disable : 4702)
@@ -28,6 +26,7 @@
#include <QDebug>
#include <vector>
+#include <tuple>
#include <cstdio>
#include <cmath>
#include <algorithm>
@@ -40,9 +39,10 @@ static const int adaptive_sizes[] =
30,
80,
#else
+ 5,
7,
9,
- 13,
+ 11,
#endif
};
@@ -56,12 +56,15 @@ static const resolution_tuple resolution_choices[] =
{
{ 640, 480 },
{ 320, 240 },
+ { 1280, 720 },
+ { 1920, 1080 },
{ 0, 0 }
};
aruco_tracker::aruco_tracker()
{
- cv::setBreakOnError(true);
+ opencv_init();
+
// param 2 ignored for Otsu thresholding. it's required to use our fork of Aruco.
set_detector_params();
}
@@ -72,7 +75,6 @@ aruco_tracker::~aruco_tracker()
wait();
// fast start/stop causes breakage
portable::sleep(1000);
- camera.release();
}
module_status aruco_tracker::start_tracker(QFrame* videoframe)
@@ -81,8 +83,8 @@ module_status aruco_tracker::start_tracker(QFrame* videoframe)
videoWidget = std::make_unique<cv_video_widget>(videoframe);
layout = std::make_unique<QHBoxLayout>();
layout->setContentsMargins(0, 0, 0, 0);
- layout->addWidget(videoWidget.get());
- videoframe->setLayout(layout.get());
+ layout->addWidget(&*videoWidget);
+ videoframe->setLayout(&*layout);
videoWidget->show();
start();
@@ -101,11 +103,12 @@ bool aruco_tracker::detect_with_roi()
{
if (last_roi.width > 1 && last_roi.height > 1)
{
- detector.setMinMaxSize(clamp(size_min * grayscale.cols / last_roi.width, .01f, 1.f),
- clamp(size_max * grayscale.cols / last_roi.width, .01f, 1.f));
+ detector.setMinMaxSize(std::clamp(size_min * grayscale.cols / last_roi.width, .01f, 1.f),
+ std::clamp(size_max * grayscale.cols / last_roi.width, .01f, 1.f));
detector.detect(grayscale(last_roi), markers, cv::Mat(), cv::Mat(), -1, false);
+ // XXX TODO maybe cache the first-present marker id and force it's the same one? -sh 20180517
if (markers.size() == 1 && markers[0].size() == 4)
{
auto& m = markers[0];
@@ -130,48 +133,56 @@ bool aruco_tracker::detect_without_roi()
return markers.size() == 1 && markers[0].size() == 4;
}
-bool aruco_tracker::open_camera()
+static int enum_to_fps(int value)
{
- int rint = s.resolution;
- if (rint < 0 || rint >= (int)(sizeof(resolution_choices) / sizeof(resolution_tuple)))
- rint = 0;
- resolution_tuple res = resolution_choices[rint];
int fps;
- switch (static_cast<int>(s.force_fps))
+
+ switch (value)
{
- default:
- case 0:
- fps = 0;
- break;
- case 1:
- fps = 30;
- break;
- case 2:
- fps = 60;
- break;
- case 3:
- fps = 75;
- break;
- case 4:
- fps = 125;
- break;
- case 5:
- fps = 200;
- break;
+ default: eval_once(qDebug() << "aruco: invalid fps enum value");
+ [[fallthrough]];
+ case fps_default: fps = 0; break;
+ case fps_30: fps = 30; break;
+ case fps_60: fps = 60; break;
+ case fps_75: fps = 75; break;
+ case fps_125: fps = 125; break;
+ case fps_200: fps = 200; break;
+ case fps_50: fps = 50; break;
+ case fps_100: fps = 100; break;
+ case fps_120: fps = 120; break;
+ case fps_300: fps = 300; break;
+ case fps_250: fps = 250; break;
}
+ return fps;
+}
+
+bool aruco_tracker::open_camera()
+{
+ int rint = std::clamp(*s.resolution, 0, (int)std::size(resolution_choices)-1);
+ resolution_tuple res = resolution_choices[rint];
+ int fps = enum_to_fps(s.force_fps);
+
QMutexLocker l(&camera_mtx);
- camera = cv::VideoCapture(camera_name_to_index(s.camera_name));
+ camera = video::make_camera(s.camera_name);
+
+ if (!camera)
+ return false;
+
+ video::impl::camera::info args {};
+
if (res.width)
{
- camera.set(cv::CAP_PROP_FRAME_WIDTH, res.width);
- camera.set(cv::CAP_PROP_FRAME_HEIGHT, res.height);
+ args.width = res.width;
+ args.height = res.height;
}
if (fps)
- camera.set(cv::CAP_PROP_FPS, fps);
+ args.fps = fps;
- if (!camera.isOpened())
+ args.use_mjpeg = s.use_mjpeg;
+
+ if (!camera->start(args))
{
qDebug() << "aruco tracker: can't open camera";
return false;
@@ -182,16 +193,16 @@ bool aruco_tracker::open_camera()
void aruco_tracker::set_intrinsics()
{
const int w = grayscale.cols, h = grayscale.rows;
- const double diag_fov = static_cast<int>(s.fov) * M_PI / 180.;
+ const double diag_fov = s.fov * M_PI / 180.;
const double fov_w = 2.*atan(tan(diag_fov/2.)/sqrt(1. + h/(double)w * h/(double)w));
const double fov_h = 2.*atan(tan(diag_fov/2.)/sqrt(1. + w/(double)h * w/(double)h));
const double focal_length_w = .5 * w / tan(.5 * fov_w);
const double focal_length_h = .5 * h / tan(.5 * fov_h);
intrinsics(0, 0) = focal_length_w;
- intrinsics(0, 2) = grayscale.cols/2;
+ intrinsics(0, 2) = grayscale.cols/2.;
intrinsics(1, 1) = focal_length_h;
- intrinsics(1, 2) = grayscale.rows/2;
+ intrinsics(1, 2) = grayscale.rows/2.;
}
void aruco_tracker::update_fps()
@@ -217,8 +228,7 @@ void aruco_tracker::draw_ar(bool ok)
}
char buf[9];
- ::snprintf(buf, sizeof(buf)-1, "Hz: %d", clamp(int(fps), 0, 9999));
- buf[sizeof(buf)-1] = '\0';
+ ::snprintf(buf, sizeof(buf), "Hz: %d", std::clamp(int(fps), 0, 9999));
cv::putText(frame, buf, cv::Point(10, 32), cv::FONT_HERSHEY_PLAIN, 2, cv::Scalar(0, 255, 0), 1);
}
@@ -227,19 +237,6 @@ void aruco_tracker::clamp_last_roi()
last_roi &= cv::Rect(0, 0, color.cols, color.rows);
}
-cv::Point3f aruco_tracker::rotate_model(float x, float y, settings::rot mode)
-{
- cv::Point3f pt(x, y, 0);
-
- if (mode)
- {
- const double theta = int(mode) * 90/4. * M_PI/180;
- pt.x = x * cos(theta) - y * sin(theta);
- pt.y = y * cos(theta) + x * sin(theta);
- }
- return pt;
-}
-
void aruco_tracker::set_points()
{
using f = float;
@@ -249,12 +246,10 @@ void aruco_tracker::set_points()
const int x1=1, x2=2, x3=3, x4=0;
- settings::rot mode = s.model_rotation;
-
- obj_points[x1] = rotate_model(-size, -size, mode);
- obj_points[x2] = rotate_model(size, -size, mode);
- obj_points[x3] = rotate_model(size, size, mode);
- obj_points[x4] = rotate_model(-size, size, mode);
+ obj_points[x1] = { -size, -size, 0 };
+ obj_points[x2] = { size, -size, 0 };
+ obj_points[x3] = { size, size, 0 };
+ obj_points[x4] = { -size, size, 0 };
for (unsigned i = 0; i < 4; i++)
obj_points[i] += cv::Point3f(hx, hy, hz);
@@ -268,7 +263,7 @@ void aruco_tracker::draw_centroid()
cv::projectPoints(centroid, rvec, tvec, intrinsics, cv::noArray(), repr2);
- cv::circle(frame, repr2[0], 4, cv::Scalar(255, 0, 255), -1);
+ cv::circle(frame, repr2[0], 4, {255, 0, 255}, -1);
}
void aruco_tracker::set_last_roi()
@@ -341,9 +336,9 @@ void aruco_tracker::set_detector_params()
detector.setThresholdParams(adaptive_sizes[adaptive_size_pos], adaptive_thres);
#else
- detector._thresMethod = aruco::MarkerDetector::CANNY;
- int value = adaptive_sizes[adaptive_size_pos];
- detector.setThresholdParams(value, value * 3);
+ detector._thresMethod = aruco::MarkerDetector::CANNY;
+ int value = adaptive_sizes[adaptive_size_pos];
+ detector.setThresholdParams(value, value * 3);
#endif
}
@@ -372,8 +367,6 @@ void aruco_tracker::cycle_detection_params()
void aruco_tracker::run()
{
- cv::setNumThreads(1);
-
if (!open_camera())
return;
@@ -385,11 +378,31 @@ void aruco_tracker::run()
{
QMutexLocker l(&camera_mtx);
- if (!camera.read(color))
+ auto [ img, res ] = camera->get_frame();
+
+ if (!res)
+ {
+ camera_mtx.unlock();
+ portable::sleep(100);
continue;
- }
+ }
- cv::cvtColor(color, grayscale, cv::COLOR_BGR2GRAY);
+ color = cv::Mat(img.height, img.width, CV_8UC(img.channels), (void*)img.data, img.stride);
+
+ switch (img.channels)
+ {
+ case 1:
+ grayscale.create(img.height, img.width, CV_8UC1);
+ color.copyTo(grayscale);
+ break;
+ case 3:
+ cv::cvtColor(color, grayscale, cv::COLOR_BGR2GRAY);
+ break;
+ default:
+ qDebug() << "aruco: can't handle" << img.channels << "color channels";
+ return;
+ }
+ }
#ifdef DEBUG_UNSHARP_MASKING
{
@@ -464,26 +477,59 @@ void aruco_tracker::data(double *data)
data[TZ] = pose[TZ];
}
+void aruco_dialog::make_fps_combobox()
+{
+ std::vector<std::tuple<int, int>> resolutions;
+ resolutions.reserve(fps_MAX);
+
+ for (int k = 0; k < fps_MAX; k++)
+ {
+ int hz = enum_to_fps(k);
+ resolutions.emplace_back(k, hz);
+ }
+
+ std::sort(resolutions.begin(), resolutions.end(), [](const auto& a, const auto& b) {
+ auto [idx1, hz1] = a;
+ auto [idx2, hz2] = b;
+
+ return hz1 < hz2;
+ });
+
+ for (auto [idx, hz] : resolutions)
+ {
+ QString name;
+
+ if (hz == 0)
+ name = tr("Default");
+ else
+ name = QString::number(hz);
+
+ ui.cameraFPS->addItem(name, idx);
+ }
+}
+
aruco_dialog::aruco_dialog() :
- calibrator(1, 0, 2)
+ calibrator(1, 0)
{
+ ui.setupUi(this);
+ //setAttribute(Qt::WA_NativeWindow, true);
+
+ make_fps_combobox();
+ tie_setting(s.force_fps, ui.cameraFPS);
+
tracker = nullptr;
calib_timer.setInterval(100);
- ui.setupUi(this);
- setAttribute(Qt::WA_NativeWindow, true);
- ui.cameraName->addItems(get_camera_names());
+
+ for (const auto& str : video::camera_names())
+ ui.cameraName->addItem(str);
+
tie_setting(s.camera_name, ui.cameraName);
tie_setting(s.resolution, ui.resolution);
- tie_setting(s.force_fps, ui.cameraFPS);
tie_setting(s.fov, ui.cameraFOV);
tie_setting(s.headpos_x, ui.cx);
tie_setting(s.headpos_y, ui.cy);
tie_setting(s.headpos_z, ui.cz);
-
- ui.model_rotation->addItem("0", int(settings::rot_zero));
- ui.model_rotation->addItem("+22.5", int(settings::rot_plus));
- ui.model_rotation->addItem("-22.5", int(settings::rot_neg));
- tie_setting(s.model_rotation, ui.model_rotation);
+ tie_setting(s.use_mjpeg, ui.use_mjpeg);
connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
@@ -492,7 +538,7 @@ aruco_dialog::aruco_dialog() :
connect(&calib_timer, SIGNAL(timeout()), this, SLOT(update_tracker_calibration()));
connect(ui.camera_settings, SIGNAL(clicked()), this, SLOT(camera_settings()));
- connect(&s.camera_name, base_value::value_changed<QString>(), this, &aruco_dialog::update_camera_settings_state);
+ connect(&s.camera_name, value_::value_changed<QString>(), this, &aruco_dialog::update_camera_settings_state);
update_camera_settings_state(s.camera_name);
}
@@ -511,11 +557,11 @@ void aruco_dialog::toggleCalibrate()
{
cleanupCalib();
- cv::Vec3d pos;
- std::tie(pos, std::ignore) = calibrator.get_estimate();
- s.headpos_x = -pos(0);
- s.headpos_y = -pos(1);
- s.headpos_z = -pos(2);
+ auto [ pos, nvals ] = calibrator.get_estimate();
+ (void) nvals;
+ s.headpos_x = (double)-pos(0);
+ s.headpos_y = (double)-pos(1);
+ s.headpos_z = (double)-pos(2);
}
}
@@ -552,15 +598,18 @@ void aruco_dialog::camera_settings()
if (tracker)
{
QMutexLocker l(&tracker->camera_mtx);
- video_property_page::show_from_capture(tracker->camera, camera_name_to_index(s.camera_name));
+ (void)tracker->camera->show_dialog();
}
else
- video_property_page::show(camera_name_to_index(s.camera_name));
+ (void)video::show_dialog(s.camera_name);
}
void aruco_dialog::update_camera_settings_state(const QString& name)
{
+ (void)name;
ui.camera_settings->setEnabled(true);
}
+settings::settings() : opts("aruco-tracker") {}
+
OPENTRACK_DECLARE_TRACKER(aruco_tracker, aruco_dialog, aruco_metadata)
diff --git a/tracker-aruco/ftnoir_tracker_aruco.h b/tracker-aruco/ftnoir_tracker_aruco.h
index b753fdec..839be6d5 100644
--- a/tracker-aruco/ftnoir_tracker_aruco.h
+++ b/tracker-aruco/ftnoir_tracker_aruco.h
@@ -13,8 +13,9 @@
#include "api/plugin-api.hpp"
#include "cv/video-widget.hpp"
#include "compat/timer.hpp"
+#include "video/camera.hpp"
-#include "include/markerdetector.h"
+#include "aruco/markerdetector.h"
#include <QObject>
#include <QThread>
@@ -27,7 +28,6 @@
#include <cinttypes>
#include <opencv2/core.hpp>
-#include <opencv2/videoio.hpp>
// value 0->1
//#define DEBUG_UNSHARP_MASKING .75
@@ -37,46 +37,51 @@
using namespace options;
-struct settings : opts {
- enum rot
- {
- rot_zero = 0,
- rot_neg = -1,
- rot_plus = +1,
- };
-
- value<int> fov;
- value<double> headpos_x, headpos_y, headpos_z;
- value<QString> camera_name;
- value<int> force_fps, resolution;
- value<rot> model_rotation;
- settings() :
- opts("aruco-tracker"),
- fov(b, "field-of-view", 56),
- headpos_x(b, "headpos-x", 0),
- headpos_y(b, "headpos-y", 0),
- headpos_z(b, "headpos-z", 0),
- camera_name(b, "camera-name", ""),
- force_fps(b, "force-fps", 0),
- resolution(b, "force-resolution", 0),
- model_rotation(b, "model-rotation", rot_zero)
- {}
+enum aruco_fps
+{
+ fps_default = 0,
+ fps_30 = 1,
+ fps_60 = 2,
+ fps_75 = 3,
+ fps_125 = 4,
+ fps_200 = 5,
+ fps_50 = 6,
+ fps_100 = 7,
+ fps_120 = 8,
+ fps_300 = 9,
+ fps_250 = 10,
+ fps_MAX = 11,
};
-class aruco_dialog;
+struct settings : opts {
+ value<double> headpos_x { b, "headpos-x", 0 },
+ headpos_y { b, "headpos-y", 0 },
+ headpos_z { b, "headpos-z", 0 };
+
+ value<QString> camera_name { b, "camera-name", ""};
+ value<int> resolution { b, "force-resolution", 0 };
+ value<int> fov { b, "field-of-view", 56 };
+ value<aruco_fps> force_fps { b, "force-fps", fps_default };
+ value<bool> use_mjpeg { b, "use-mjpeg", false };
+
+ settings();
+};
class aruco_tracker : protected virtual QThread, public ITracker
{
Q_OBJECT
- friend class aruco_dialog;
- static constexpr inline float c_search_window = 1.3f;
+ static constexpr float c_search_window = 1.3f;
public:
aruco_tracker();
~aruco_tracker() override;
module_status start_tracker(QFrame* frame) override;
void data(double *data) override;
void run() override;
+
void getRT(cv::Matx33d &r, cv::Vec3d &t);
+ QMutex camera_mtx;
+ std::unique_ptr<video::impl::camera> camera;
+
private:
bool detect_with_roi();
bool detect_without_roi();
@@ -93,54 +98,48 @@ private:
void set_detector_params();
void cycle_detection_params();
- cv::Point3f rotate_model(float x, float y, settings::rot mode);
-
- cv::VideoCapture camera;
- QMutex camera_mtx;
QMutex mtx;
std::unique_ptr<cv_video_widget> videoWidget;
std::unique_ptr<QHBoxLayout> layout;
settings s;
double pose[6] {}, fps = 0;
double no_detection_timeout = 0;
- cv::Mat frame, grayscale, color;
cv::Matx33d r;
-#ifdef DEBUG_UNSHARP_MASKING
- cv::Mat blurred;
-#endif
- std::vector<cv::Point3f> obj_points {4};
cv::Matx33d intrinsics = cv::Matx33d::eye();
- aruco::MarkerDetector detector;
- std::vector<aruco::Marker> markers;
cv::Vec3d t;
cv::Vec3d rvec, tvec;
- std::vector<cv::Point2f> roi_projection;
- std::vector<cv::Point2f> repr2;
cv::Matx33d m_r, m_q, rmat = cv::Matx33d::eye();
cv::Vec3d euler;
std::vector<cv::Point3f> roi_points {4};
+ std::vector<cv::Point2f> roi_projection;
+ std::vector<cv::Point2f> repr2;
+ std::vector<cv::Point3f> obj_points {4};
+ aruco::MarkerDetector detector;
+ std::vector<aruco::Marker> markers;
+ cv::Mat frame, grayscale, color;
cv::Rect last_roi { 65535, 65535, 0, 0 };
Timer fps_timer, last_detection_timer;
- unsigned adaptive_size_pos = 0;
+ unsigned adaptive_size_pos { 0 };
bool use_otsu = false;
#if !defined USE_EXPERIMENTAL_CANNY
- static constexpr inline int adaptive_thres = 6;
+ static constexpr int adaptive_thres = 6;
#else
- static constexpr inline int adaptive_thres = 3;
+ static constexpr int adaptive_thres = 3;
#endif
-#ifdef DEBUG_UNSHARP_MASKING
- static constexpr inline double gauss_kernel_size = 3;
-#endif
+ static constexpr double timeout = .35;
+ static constexpr double timeout_backoff_c = .25;
- static constexpr inline double timeout = 1;
- static constexpr inline double timeout_backoff_c = 4./11;
+ static constexpr float size_min = 0.05f;
+ static constexpr float size_max = 0.5f;
- static constexpr inline float size_min = 0.05;
- static constexpr inline float size_max = 0.5;
+ static constexpr double RC = .25;
- static constexpr inline double RC = .25;
+#ifdef DEBUG_UNSHARP_MASKING
+ static constexpr double gauss_kernel_size = 3;
+ cv::Mat blurred;
+#endif
};
class aruco_dialog : public ITrackerDialog
@@ -151,6 +150,8 @@ public:
void register_tracker(ITracker * x) override { tracker = static_cast<aruco_tracker*>(x); }
void unregister_tracker() override { tracker = nullptr; }
private:
+ void make_fps_combobox();
+
Ui::Form ui;
aruco_tracker* tracker;
settings s;
@@ -168,6 +169,7 @@ private Q_SLOTS:
class aruco_metadata : public Metadata
{
- QString name() { return QString("aruco -- paper marker tracker"); }
- QIcon icon() { return QIcon(":/images/aruco.png"); }
+ Q_OBJECT
+ QString name() override { return QString("aruco -- paper marker tracker"); }
+ QIcon icon() override { return QIcon(":/images/aruco.png"); }
};
diff --git a/tracker-aruco/include/aruco.h b/tracker-aruco/include/aruco.h
deleted file mode 100644
index 569b95fb..00000000
--- a/tracker-aruco/include/aruco.h
+++ /dev/null
@@ -1,134 +0,0 @@
-/**
-
-Copyright 2011 Rafael Muñoz Salinas. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice, this list of
- conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice, this list
- of conditions and the following disclaimer in the documentation and/or other materials
- provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY Rafael Muñoz Salinas ''AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rafael Muñoz Salinas OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Rafael Muñoz Salinas.
-
-
-
- \mainpage ArUco: Augmented Reality library from the University of Cordoba
-
-
-ArUco is a minimal C++ library for detection of Augmented Reality markers based on OpenCv exclusively.
-
-It is an educational project to show student how to detect augmented reality markers and it is provided under BSD license.
-
-
-\section INTRODUCTION INTRODUCTION
-
-The library relies on the use of coded markers. Each marker has an unique code indicated by the black and white colors in it. The libary detect borders, and analyzes into the rectangular regions which of them are likely to be markers. Then, a decoding is performed and if the code is valid, it is considered that the rectangle is a marker.
-
-The codification included into the marker is a slighly modified version of the Hamming Code. It has a total a 25 bits didived in 5 rows of 5 bits each. So, we have 5 words of 5 bits. Each word, contains only 2 bits of real information, the rest is for and error detection/correction (error correction is yet to be done). As a conclusion, a marker contains 10 bits of real information wich allows 1024 different markers.
-
-
-\section BOARDS BOARDS
-
-Aruco allows the possibility to employ board. Boards are markers composed by an array of markers arranged in a known order. The advantages of using boards instead of simple markers are:
- - More robusteness. The misdetection of several markers of the board is not a problem as long as a minimum set of them are detected.
- - More precision. Since there are a larger number of corners, camera pose estimation becomes more precise.
-
-
-\section APPLICATIONS APPLICATIONS
-
-The library comes with five applications that will help you to learn how to use the library:
- - aruco_create_marker: which creates marker and saves it in a jpg file you can print.
- - aruco_simple : simple test aplication that detects the markers in a image
- - aruco_test: this is the main application for detection. It reads images either from the camera of from a video and detect markers. Additionally, if you provide the intrinsics of the camera(obtained by OpenCv calibration) and the size of the marker in meters, the library calculates the marker intrinsics so that you can easily create your AR applications.
- - aruco_test_gl: shows how to use the library AR applications using OpenGL for rendering
- - aruco_create_board: application that helps you to create a board
- - aruco_simple_board: simple test aplication that detects a board of markers in a image
- - aruco_test_board: application that detects boards
- - aruco_test_board_gl: application that detects boards and uses OpenGL to draw
-
-\section LIBRARY LIBRARY DESCRIPTION:
-
-The ArUco library contents are divided in two main directories. The src directory, which contains the library itself. And the utils directory which contains the applications.
-
-The library main classes are:
- - aruco::CameraParameters: represent the information of the camera that captures the images. Here you must set the calibration info.
- - aruco::Marker: which represent a marker detected in the image
- - aruco::MarkerDetector: that is in charge of deteting the markers in a image Detection is done by simple calling the member funcion ArMarkerDetector::detect(). Additionally, the classes contain members to create the required matrices for rendering using OpenGL. See aruco_test_gl for details
- - aruco::BoardConfiguration: A board is an array of markers in a known order. BoardConfiguracion is the class that defines a board by indicating the id of its markers. In addition, it has informacion about the distance between the markers so that extrinsica camera computations can be done.
- - aruco::Board: This class defines a board detected in a image. The board has the extrinsic camera parameters as public atributes. In addition, it has a method that allows obtain the matrix for getting its position in OpenGL (see aruco_test_board_gl for details).
- - aruco::BoardDetector : This is the class in charge of detecting a board in a image. You must pass to it the set of markers detected by ArMarkerDetector and the BoardConfiguracion of the board you want to detect. This class will do the rest for you, even calculating the camera extrinsics.
-
-
-\section COMPILING COMPILING THE LIBRARY:
-\subsection Linux
-Go to the aruco library and do
-\verbatim
->mkdir build
->cd build
->cmake ..
->make
->make install (optional)
-\endverbatim
-
-NOTE ON OPENGL: The library supports eaily the integration with OpenGL. In order to compile with support for OpenGL, you just have installed in your system the develop packages for GL and glut (or freeglut).
-
-\subsection WINDOWS
-
-The library has been compiled using MinGW and codeblocks. Below I describe the best way to compile it that I know. If you know better, please let me know.
- - step 1) codeblocks
- -# Download codeblocks. I recommend to download the version 10.5 with mingw included (codeblocks-10.05mingw-setup.exe)
- -# Install and set the PATH variable so that the codeblock/mingw/bin directory is included. In my case c:/codeblocks/mingw/bin. This will allow cmake to find the compiler.
- -# The codeblock program will not find the mingw path by deafult. So, run codeblocks and go to setting->Compuiler debugger and set the correct path to the MinGW dir.
- - step 2) cmake
- -# Download and install the last version of cmake.
- - step 3) OpenCv
- -# Download the source code and compile it using cmake and codeblocks. Note: install the library in C:\ if you want it to be easily detected by cmake afterwards
- - step 4) aruco
- -# Download and decompress.
- -# Open cmake gui application and set the path to the main library directory and also set a path where the project is going to be built.
- -# Generate the codeblock project.
- -# Open the project with codeblock and compile then, install. The programs will be probably generated into the bin directory
-
-OpenGL: by default, the mingw version installed has not the glut library. So, the opengl programs are not compiled. If you want to compile with OpenGL support, you must install glut, or prefereably freeglut.
-Thus,
- - Download the library (http://www.martinpayne.me.uk/software/development/GLUT/freeglut-MinGW.zip) for mingw.
- - Decompress in a directory X.
- - Then, rerun cmake setting the variable GLU_PATH to that directory (>cmake .. -DGLUT_PATH="C:\X")
- - Finally, recompile and test. Indeed, you should move the freeglut.dll to the directory with the binaries or to any other place in the PATH.
-
-
-CONCLUSION: Move to Linux, things are simpler :P
-
-
-\section Testing
-
-For testing the applications, the library provides videos and the corresponding camera parameters of these videos. Into the directories you will find information on how to run the examples.
-
-\section Final Notes
-
- - REQUIREMENTS: OpenCv >= 2.1.0. and OpenGL for (aruco_test_gl and aruco_test_board_gl)
- - CONTACT: Rafael Munoz-Salinas: rmsalinas@uco.es
- - This libary is free software and come with no guaratee!
-
-*/
-
-#include "markerdetector.h"
-#include "boarddetector.h"
-#include "cvdrawingutils.h"
-
diff --git a/tracker-aruco/include/arucofidmarkers.h b/tracker-aruco/include/arucofidmarkers.h
deleted file mode 100644
index 2577bf92..00000000
--- a/tracker-aruco/include/arucofidmarkers.h
+++ /dev/null
@@ -1,119 +0,0 @@
-/*****************************
-Copyright 2011 Rafael Muñoz Salinas. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice, this list of
- conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice, this list
- of conditions and the following disclaimer in the documentation and/or other materials
- provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY Rafael Muñoz Salinas ''AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rafael Muñoz Salinas OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Rafael Muñoz Salinas.
-********************************/
-
-#ifndef ArucoFiducicalMarkerDetector_H
-#define ArucoFiducicalMarkerDetector_H
-#include <opencv2/core.hpp>
-#include "exports.h"
-#include "marker.h"
-#include "board.h"
-namespace aruco {
-
-class ARUCO_EXPORTS FiducidalMarkers {
-public:
- /**
- * \brief Creates an ar marker with the id specified using a modified version of the hamming code.
- * There are two type of markers: a) These of 10 bits b) these of 3 bits. The latter are employed for applications
- * that need few marker but they must be small. The two type of markers are distinguished by their ids. While the first type
- * of markers have ids in the interval [0-1023], the second type ids in the interval [2000-2006].
- *
- *
- * 10 bits markers
- * -----------------------
- * There are a total of 5 rows of 5 cols. Each row encodes a total of 2 bits, so there are 2^10 bits:(0-1023).
- *
- * The least significative bytes are first (from left-up to to right-bottom)
- *
- * Example: the id = 110 (decimal) is be represented in binary as : 00 01 10 11 10.
- *
- * Then, it will generate the following marker:
- *
- * -# 1st row encodes 00: 1 0 0 0 0 : hex 0x10
- * -# 2nd row encodes 01: 1 0 1 1 1 : hex 0x17
- * -# 3nd row encodes 10: 0 1 0 0 1 : hex 0x09
- * -# 4th row encodes 11: 0 1 1 1 0 : hex 0x0e
- * -# 5th row encodes 10: 0 1 0 0 1 : hex 0x09
- *
- * Note that : The first bit, is the inverse of the hamming parity. This avoids the 0 0 0 0 0 to be valid
- * These marker are detected by the function getFiduciadlMarker_Aruco_Type1
- */
- static cv::Mat createMarkerImage(int id,int size) throw (cv::Exception);
-
- /** Detection of fiducidal aruco markers (10 bits)
- * @param in input image with the patch that contains the possible marker
- * @param nRotations number of 90deg rotations in clockwise direction needed to set the marker in correct position
- * @return -1 if the image passed is a not a valid marker, and its id in case it really is a marker
- */
- static int detect(const cv::Mat &in,int &nRotations);
-
- /**Similar to createMarkerImage. Instead of returning a visible image, returns a 8UC1 matrix of 0s and 1s with the marker info
- */
- static cv::Mat getMarkerMat(int id) throw (cv::Exception);
-
-
- /**Creates a printable image of a board
- * @param gridSize grid layout (numer of sqaures in x and Y)
- * @param MarkerSize size of markers sides in pixels
- * @param MarkerDistance distance between the markers
- * @param TInfo output
- * @param excludedIds set of ids excluded from the board
- */
- static cv::Mat createBoardImage( cv::Size gridSize,int MarkerSize,int MarkerDistance, BoardConfiguration& TInfo ,vector<int> *excludedIds=NULL ) throw (cv::Exception);
-
-
- /**Creates a printable image of a board in chessboard_like manner
- * @param gridSize grid layout (numer of sqaures in x and Y)
- * @param MarkerSize size of markers sides in pixels
- * @param TInfo output
- * @param setDataCentered indicates if the center is set at the center of the board. Otherwise it is the left-upper corner
- *
- */
- static cv::Mat createBoardImage_ChessBoard( cv::Size gridSize,int MarkerSize, BoardConfiguration& TInfo ,bool setDataCentered=true ,vector<int> *excludedIds=NULL) throw (cv::Exception);
-
- /**Creates a printable image of a board in a frame fashion
- * @param gridSize grid layout (numer of sqaures in x and Y)
- * @param MarkerSize size of markers sides in pixels
- * @param MarkerDistance distance between the markers
- * @param TInfo output
- * @param setDataCentered indicates if the center is set at the center of the board. Otherwise it is the left-upper corner
- *
- */
- static cv::Mat createBoardImage_Frame( cv::Size gridSize,int MarkerSize,int MarkerDistance, BoardConfiguration& TInfo ,bool setDataCentered=true,vector<int> *excludedIds=NULL ) throw (cv::Exception);
-
-private:
-
- static vector<int> getListOfValidMarkersIds_random(int nMarkers,vector<int> *excluded) throw (cv::Exception);
- static cv::Mat rotate(const cv::Mat & in);
- static int hammDistMarker(cv::Mat bits);
- static int analyzeMarkerImage(cv::Mat &grey,int &nRotations);
- static bool correctHammMarker(cv::Mat &bits);
-};
-
-}
-
-#endif
diff --git a/tracker-aruco/include/board.h b/tracker-aruco/include/board.h
deleted file mode 100644
index ec7dd43f..00000000
--- a/tracker-aruco/include/board.h
+++ /dev/null
@@ -1,168 +0,0 @@
-/*****************************
-Copyright 2011 Rafael Muñoz Salinas. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice, this list of
- conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice, this list
- of conditions and the following disclaimer in the documentation and/or other materials
- provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY Rafael Muñoz Salinas ''AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rafael Muñoz Salinas OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Rafael Muñoz Salinas.
-********************************/
-#ifndef _Aruco_board_h
-#define _Aruco_board_h
-#include <opencv2/opencv.hpp>
-#include <string>
-#include <vector>
-#include "exports.h"
-#include "marker.h"
-namespace aruco {
-using namespace std;
-/**
- * 3d representation of a marker
- */
-struct ARUCO_EXPORTS MarkerInfo:public vector<cv::Point3f> {
- MarkerInfo() {}
- MarkerInfo(int _id) {id=_id; }
- MarkerInfo(const MarkerInfo&MI): vector<cv::Point3f>(MI){id=MI.id; }
- MarkerInfo & operator=(const MarkerInfo&MI){
- vector<cv::Point3f> ::operator=(MI);
- id=MI.id;
- return *this;
- }
- int id;//maker id
-};
-
-/**\brief This class defines a board with several markers.
- * A Board contains several markers so that they are more robustly detected.
- *
- * In general, a board is a set of markers. So BoardConfiguration is only a list
- * of the id of the markers along with the position of their corners.
- *
- * The position of the corners can be specified either in pixels (in a non-specific size) or in meters.
- * The first is the typical case in which you generate the image of board and the print it. Since you do not know in advance the real
- * size of the markers, their corners are specified in pixels, and then, the translation to meters can be made once you know the real size.
- *
- * On the other hand, you may want to have the information of your boards in meters. The BoardConfiguration allows you to do so.
- *
- * The point is in the mInfoType variable. It can be either PIX or METERS according to your needs.
- *
-*/
-
-
-class ARUCO_EXPORTS BoardConfiguration: public vector<MarkerInfo>
-{
- friend class Board;
-public:
- enum MarkerInfoType {NONE=-1,PIX=0,METERS=1};//indicates if the data in MakersInfo is expressed in meters or in pixels so as to do conversion internally
- //variable indicates if the data in MakersInfo is expressed in meters or in pixels so as to do conversion internally
- int mInfoType;
- /**
- */
- BoardConfiguration();
-
- /**
- */
- BoardConfiguration(const BoardConfiguration &T);
-
- /**
- */
- BoardConfiguration & operator=(const BoardConfiguration &T);
- /**Saves the board info to a file
- */
- void saveToFile(string sfile)throw (cv::Exception);
- /**Reads board info from a file
- */
- void readFromFile(string sfile)throw (cv::Exception);
- /**Indicates if the corners are expressed in meters
- */
- bool isExpressedInMeters()const {
- return mInfoType==METERS;
- }
- /**Indicates if the corners are expressed in meters
- */
- bool isExpressedInPixels()const {
- return mInfoType==PIX;
- }
- /**Returns the index of the marker with id indicated, if is in the list
- */
- int getIndexOfMarkerId(int id)const;
- /**Returns the Info of the marker with id specified. If not in the set, throws exception
- */
- const MarkerInfo& getMarkerInfo(int id)const throw (cv::Exception);
- /**Set in the list passed the set of the ids
- */
- void getIdList(vector<int> &ids,bool append=true)const;
-private:
- /**Saves the board info to a file
- */
- void saveToFile(cv::FileStorage &fs)throw (cv::Exception);
- /**Reads board info from a file
- */
- void readFromFile(cv::FileStorage &fs)throw (cv::Exception);
-};
-
-/**
-*/
-class ARUCO_EXPORTS Board:public vector<Marker>
-{
-
-public:
- BoardConfiguration conf;
- //matrices of rotation and translation respect to the camera
- cv::Mat Rvec,Tvec;
- /**
- */
- Board()
- {
- Rvec.create(3,1,CV_32FC1);
- Tvec.create(3,1,CV_32FC1);
- for (int i=0;i<3;i++)
- Tvec.at<float>(i,0)=Rvec.at<float>(i,0)=-999999;
- }
-
- /**Given the extrinsic camera parameters returns the GL_MODELVIEW matrix for opengl.
- * Setting this matrix, the reference corrdinate system will be set in this board
- */
- void glGetModelViewMatrix(double modelview_matrix[16])throw(cv::Exception);
-
- /**
- * Returns position vector and orientation quaternion for an Ogre scene node or entity.
- * Use:
- * ...
- * Ogre::Vector3 ogrePos (position[0], position[1], position[2]);
- * Ogre::Quaternion ogreOrient (orientation[0], orientation[1], orientation[2], orientation[3]);
- * mySceneNode->setPosition( ogrePos );
- * mySceneNode->setOrientation( ogreOrient );
- * ...
- */
- void OgreGetPoseParameters( double position[3], double orientation[4] )throw(cv::Exception);
-
-
- /**Save this from a file
- */
- void saveToFile(string filePath)throw(cv::Exception);
- /**Read this from a file
- */
- void readFromFile(string filePath)throw(cv::Exception);
-
-};
-}
-
-#endif
diff --git a/tracker-aruco/include/boarddetector.h b/tracker-aruco/include/boarddetector.h
deleted file mode 100644
index 619b4798..00000000
--- a/tracker-aruco/include/boarddetector.h
+++ /dev/null
@@ -1,138 +0,0 @@
-/*****************************
-Copyright 2011 Rafael Muñoz Salinas. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice, this list of
- conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice, this list
- of conditions and the following disclaimer in the documentation and/or other materials
- provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY Rafael Muñoz Salinas ''AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rafael Muñoz Salinas OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Rafael Muñoz Salinas.
-********************************/
-#ifndef _Aruco_BoardDetector_H
-#define _Aruco_BoardDetector_H
-#include <opencv2/opencv.hpp>
-#include "exports.h"
-#include "board.h"
-#include "cameraparameters.h"
-#include "markerdetector.h"
-
-namespace aruco
-{
-using namespace std;
-/**\brief This class detects AR boards
- * Version 1.2
- * There are two modes for board detection.
- * First, the old way. (You first detect markers with MarkerDetector and then call to detect in this class.
- *
- * Second: New mode, marker detection is included in the class
- * \code
-
- CameraParameters CP;
- CP.readFromFile(path_cp)
- BoardConfiguration BC;
- BC.readFromFile(path_bc);
- BoardDetector BD;
- BD.setParams(BC,CP); //or only BD.setParams(BC)
- //capture image
- cv::Mat im;
- capture_image(im);
-
- float prob=BD.detect(im);
- if (prob>0.3)
- CvDrawingUtils::draw3DAxis(im,BD.getDetectedBoard(),CP);
-
- \endcode
- *
-*/
-class ARUCO_EXPORTS BoardDetector
-{
-public:
- /** See discussion in @see enableRotateXAxis.
- * Do not change unless you know what you are doing
- */
- BoardDetector(bool setYPerperdicular=true);
-
-
- /**
- * Use if you plan to let this class to perform marker detection too
- */
- void setParams(const BoardConfiguration &bc,const CameraParameters &cp, float markerSizeMeters=-1);
- void setParams(const BoardConfiguration &bc);
- /**
- * Detect markers, and then, look for the board indicated in setParams()
- * @return value indicating the likelihood of having found the marker
- */
- float detect(const cv::Mat &im)throw (cv::Exception);
- /**Returns a reference to the board detected
- */
- Board & getDetectedBoard(){return _boardDetected;}
- /**Returns a reference to the internal marker detector
- */
- MarkerDetector &getMarkerDetector(){return _mdetector;}
- /**Returns the vector of markers detected
- */
- vector<Marker> &getDetectedMarkers(){return _vmarkers;}
-
-
- //ALTERNATIVE DETECTION METHOD, BASED ON MARKERS PREVIOUSLY DETECTED
-
- /** Given the markers detected, determines if there is the board passed
- * @param detectedMarkers result provided by aruco::ArMarkerDetector
- * @param BConf the board you want to see if is present
- * @param Bdetected output information of the detected board
- * @param camMatrix camera matrix with intrinsics
- * @param distCoeff camera distorsion coeff
- * @param camMatrix intrinsic camera information.
- * @param distCoeff camera distorsion coefficient. If set Mat() if is assumed no camera distorion
- * @param markerSizeMeters size of the marker sides expressed in meters
- * @return value indicating the likelihood of having found the marker
- */
- float detect(const vector<Marker> &detectedMarkers,const BoardConfiguration &BConf, Board &Bdetected, cv::Mat camMatrix=cv::Mat(),cv::Mat distCoeff=cv::Mat(), float markerSizeMeters=-1 )throw (cv::Exception);
- float detect(const vector<Marker> &detectedMarkers,const BoardConfiguration &BConf, Board &Bdetected,const CameraParameters &cp, float markerSizeMeters=-1 )throw (cv::Exception);
-
-
- /**
- * By default, the Y axis is set to point up. However this is not the default
- * operation mode of opencv, which produces the Z axis pointing up instead.
- * So, to achieve this change, we have to rotate the X axis.
- */
- void setYPerperdicular(bool enable){_setYPerperdicular=enable;}
-
-
-
-
-private:
- void rotateXAxis(cv::Mat &rotation);
- bool _setYPerperdicular;
-
- //-- Functionality to detect markers inside
- bool _areParamsSet;
- BoardConfiguration _bconf;
- Board _boardDetected;
- float _markerSize;
- CameraParameters _camParams;
- MarkerDetector _mdetector;//internal markerdetector
- vector<Marker> _vmarkers;//markers detected in the call to : float detect(const cv::Mat &im);
-
-};
-
-};
-#endif
-
diff --git a/tracker-aruco/include/cameraparameters.h b/tracker-aruco/include/cameraparameters.h
deleted file mode 100644
index 0f0fd329..00000000
--- a/tracker-aruco/include/cameraparameters.h
+++ /dev/null
@@ -1,138 +0,0 @@
-/*****************************
-Copyright 2011 Rafael Muñoz Salinas. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice, this list of
- conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice, this list
- of conditions and the following disclaimer in the documentation and/or other materials
- provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY Rafael Muñoz Salinas ''AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rafael Muñoz Salinas OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Rafael Muñoz Salinas.
-********************************/
-#ifndef _Aruco_CameraParameters_H
-#define _Aruco_CameraParameters_H
-#include "exports.h"
-#include <opencv2/core.hpp>
-#include <string>
-
-namespace aruco
-{
-using namespace std;
-/**\brief Parameters of the camera
- */
-
-class ARUCO_EXPORTS CameraParameters
-{
-public:
-
- // 3x3 matrix (fx 0 cx, 0 fy cy, 0 0 1)
- cv::Mat CameraMatrix;
- //4x1 matrix (k1,k2,p1,p2)
- cv::Mat Distorsion;
- //size of the image
- cv::Size CamSize;
-
- /**Empty constructor
- */
- CameraParameters() ;
- /**Creates the object from the info passed
- * @param cameraMatrix 3x3 matrix (fx 0 cx, 0 fy cy, 0 0 1)
- * @param distorsionCoeff 4x1 matrix (k1,k2,p1,p2)
- * @param size image size
- */
- CameraParameters(cv::Mat cameraMatrix,cv::Mat distorsionCoeff,cv::Size size) noexcept;
- /**Sets the parameters
- * @param cameraMatrix 3x3 matrix (fx 0 cx, 0 fy cy, 0 0 1)
- * @param distorsionCoeff 4x1 matrix (k1,k2,p1,p2)
- * @param size image size
- */
- void setParams(cv::Mat cameraMatrix,cv::Mat distorsionCoeff,cv::Size size) noexcept;
- /**Copy constructor
- */
- CameraParameters(const CameraParameters &CI) ;
-
- /**Indicates whether this object is valid
- */
- bool isValid()const {
- return CameraMatrix.rows!=0 && CameraMatrix.cols!=0 && Distorsion.rows!=0 && Distorsion.cols!=0 && CamSize.width!=-1 && CamSize.height!=-1;
- }
- /**Assign operator
- */
- CameraParameters & operator=(const CameraParameters &CI);
- /**Reads the camera parameters from a file generated using saveToFile.
- */
- void readFromFile(string path)noexcept;
- /**Saves this to a file
- */
- void saveToFile(string path,bool inXML=true)noexcept;
-
- /**Reads from a YAML file generated with the opencv2.2 calibration utility
- */
- void readFromXMLFile(string filePath)noexcept;
-
- /**Adjust the parameters to the size of the image indicated
- */
- void resize(cv::Size size)noexcept;
-
- /**Returns the location of the camera in the reference system given by the rotation and translation vectors passed
- * NOT TESTED
- */
- static cv::Point3f getCameraLocation(cv::Mat Rvec,cv::Mat Tvec);
-
- /**Given the intrinsic camera parameters returns the GL_PROJECTION matrix for opengl.
- * PLease NOTE that when using OpenGL, it is assumed no camera distorsion! So, if it is not true, you should have
- * undistor image
- *
- * @param orgImgSize size of the original image
- * @param size of the image/window where to render (can be different from the real camera image). Please not that it must be related to CamMatrix
- * @param proj_matrix output projection matrix to give to opengl
- * @param gnear,gfar: visible rendering range
- * @param invert: indicates if the output projection matrix has to yield a horizontally inverted image because image data has not been stored in the order of glDrawPixels: bottom-to-top.
- */
- void glGetProjectionMatrix( cv::Size orgImgSize, cv::Size size,double proj_matrix[16],double gnear,double gfar,bool invert=false )noexcept;
-
- /**
- * setup camera for an Ogre project.
- * Use:
- * ...
- * Ogre::Matrix4 PM(proj_matrix[0], proj_matrix[1], ... , proj_matrix[15]);
- * yourCamera->setCustomProjectionMatrix(true, PM);
- * yourCamera->setCustomViewMatrix(true, Ogre::Matrix4::IDENTITY);
- * ...
- * As in OpenGL, it assumes no camera distorsion
- */
- void OgreGetProjectionMatrix( cv::Size orgImgSize, cv::Size size,double proj_matrix[16],double gnear,double gfar,bool invert=false )noexcept;
-
-
-private:
- //GL routines
-
- static void argConvGLcpara2( double cparam[3][4], int width, int height, double gnear, double gfar, double m[16], bool invert )noexcept;
- static int arParamDecompMat( double source[3][4], double cpara[3][4], double trans[3][4] )noexcept;
- static double norm( double a, double b, double c );
- static double dot( double a1, double a2, double a3,
- double b1, double b2, double b3 );
-
-
-};
-
-}
-#endif
-
-
diff --git a/tracker-aruco/include/cvdrawingutils.h b/tracker-aruco/include/cvdrawingutils.h
deleted file mode 100644
index ff67242f..00000000
--- a/tracker-aruco/include/cvdrawingutils.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*****************************
-Copyright 2011 Rafael Muñoz Salinas. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice, this list of
- conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice, this list
- of conditions and the following disclaimer in the documentation and/or other materials
- provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY Rafael Muñoz Salinas ''AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rafael Muñoz Salinas OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Rafael Muñoz Salinas.
-********************************/
-#ifndef _ArUco_DrawUtils_H_
-#define _ArUco_DrawUtils_H_
-#include "exports.h"
-#include "aruco.h"
-namespace aruco
-{
- /**\brief A set of functions to draw in opencv images
- */
- class ARUCO_EXPORTS CvDrawingUtils
- {
- public:
-
- static void draw3dAxis(cv::Mat &Image,Marker &m,const CameraParameters &CP);
-
- static void draw3dCube(cv::Mat &Image,Marker &m,const CameraParameters &CP);
-
- static void draw3dAxis(cv::Mat &Image,Board &m,const CameraParameters &CP);
-
- static void draw3dCube(cv::Mat &Image,Board &m,const CameraParameters &CP);
-
- };
-};
-
-#endif
-
diff --git a/tracker-aruco/include/exports.h b/tracker-aruco/include/exports.h
deleted file mode 100644
index 8f7dab8c..00000000
--- a/tracker-aruco/include/exports.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*****************************
-Copyright 2011 Rafael Muñoz Salinas. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice, this list of
- conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice, this list
- of conditions and the following disclaimer in the documentation and/or other materials
- provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY Rafael Muñoz Salinas ''AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rafael Muñoz Salinas OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Rafael Muñoz Salinas.
-********************************/
-
-
-
-#ifndef __OPENARUCO_CORE_TYPES_H__
-#define __OPENARUCO_CORE_TYPES_H__
-
-#if !defined _CRT_SECURE_NO_DEPRECATE && _MSC_VER > 1300
-#define _CRT_SECURE_NO_DEPRECATE /* to avoid multiple Visual Studio 2005 warnings */
-#endif
-
-
-#if ((defined WIN32 || defined _WIN32 || defined WINCE) && defined DSO_EXPORTS) || defined(_MSC_VER)
- #define ARUCO_EXPORTS __declspec(dllexport)
-#else
- #define ARUCO_EXPORTS __attribute__ ((visibility ("default")))
-#endif
-
-
-#endif
diff --git a/tracker-aruco/include/marker.h b/tracker-aruco/include/marker.h
deleted file mode 100644
index 360eae72..00000000
--- a/tracker-aruco/include/marker.h
+++ /dev/null
@@ -1,143 +0,0 @@
-/*****************************
-Copyright 2011 Rafael Muñoz Salinas. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice, this list of
- conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice, this list
- of conditions and the following disclaimer in the documentation and/or other materials
- provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY Rafael Muñoz Salinas ''AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rafael Muñoz Salinas OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Rafael Muñoz Salinas.
-********************************/
-#ifndef _Aruco_Marker_H
-#define _Aruco_Marker_H
-#include <vector>
-#include <iostream>
-#include <opencv2/core.hpp>
-#include "exports.h"
-#include "cameraparameters.h"
-namespace aruco {
-using namespace std;
-/**\brief This class represents a marker. It is a vector of the fours corners ot the marker
- *
- */
-
-class ARUCO_EXPORTS Marker: public std::vector<cv::Point2f>
-{
-public:
- //id of the marker
- int id;
- //size of the markers sides in meters
- float ssize;
- //matrices of rotation and translation respect to the camera
- cv::Mat Rvec,Tvec;
-
- /**
- */
- Marker();
- /**
- */
- Marker(const Marker &M);
- /**
- */
- Marker(const std::vector<cv::Point2f> &corners,int _id=-1);
- /**
- */
- ~Marker() {}
- /**Indicates if this object is valid
- */
- bool isValid()const{return id!=-1 && size()==4;}
-
- /**Draws this marker in the input image
- */
- void draw(cv::Mat &in, cv::Scalar color, int lineWidth=1,bool writeId=true)const;
-
- /**Calculates the extrinsics (Rvec and Tvec) of the marker with respect to the camera
- * @param markerSize size of the marker side expressed in meters
- * @param CP parmeters of the camera
- * @param setYPerperdicular If set the Y axis will be perpendicular to the surface. Otherwise, it will be the Z axis
- */
- void calculateExtrinsics(float markerSize,const CameraParameters &CP,bool setYPerperdicular=true)noexcept;
- /**Calculates the extrinsics (Rvec and Tvec) of the marker with respect to the camera
- * @param markerSize size of the marker side expressed in meters
- * @param CameraMatrix matrix with camera parameters (fx,fy,cx,cy)
- * @param Distorsion matrix with distorsion parameters (k1,k2,p1,p2)
- * @param setYPerperdicular If set the Y axis will be perpendicular to the surface. Otherwise, it will be the Z axis
- */
- void calculateExtrinsics(float markerSize,cv::Mat CameraMatrix,cv::Mat Distorsion=cv::Mat(),bool setYPerperdicular=true)noexcept;
-
- /**Given the extrinsic camera parameters returns the GL_MODELVIEW matrix for opengl.
- * Setting this matrix, the reference coordinate system will be set in this marker
- */
- void glGetModelViewMatrix( double modelview_matrix[16])noexcept;
-
- /**
- * Returns position vector and orientation quaternion for an Ogre scene node or entity.
- * Use:
- * ...
- * Ogre::Vector3 ogrePos (position[0], position[1], position[2]);
- * Ogre::Quaternion ogreOrient (orientation[0], orientation[1], orientation[2], orientation[3]);
- * mySceneNode->setPosition( ogrePos );
- * mySceneNode->setOrientation( ogreOrient );
- * ...
- */
- void OgreGetPoseParameters( double position[3], double orientation[4] )noexcept;
-
- /**Returns the centroid of the marker
- */
- cv::Point2f getCenter()const;
- /**Returns the perimeter of the marker
- */
- float getPerimeter()const;
- /**Returns the area
- */
- float getArea()const;
- /**
- */
- /**
- */
- friend bool operator<(const Marker &M1,const Marker&M2)
- {
- return M1.id<M2.id;
- }
- /**
- */
- friend ostream & operator<<(ostream &str,const Marker &M)
- {
- str<<M.id<<"=";
- for (int i=0;i<4;i++)
- str<<"("<<M[i].x<< ","<<M[i].y<<") ";
- str<<"Txyz=";
- for (int i=0;i<3;i++)
- str<<M.Tvec.ptr<float>(0)[i]<<" ";
- str<<"Rxyz=";
- for (int i=0;i<3;i++)
- str<<M.Rvec.ptr<float>(0)[i]<<" ";
-
- return str;
- }
-
-
-private:
- void rotateXAxis(cv::Mat &rotation);
-
-};
-
-}
-#endif
diff --git a/tracker-aruco/include/markerdetector.h b/tracker-aruco/include/markerdetector.h
deleted file mode 100644
index 4e5f74c3..00000000
--- a/tracker-aruco/include/markerdetector.h
+++ /dev/null
@@ -1,360 +0,0 @@
-/*****************************
-Copyright 2011 Rafael Muñoz Salinas. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice, this list of
- conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice, this list
- of conditions and the following disclaimer in the documentation and/or other materials
- provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY Rafael Muñoz Salinas ''AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rafael Muñoz Salinas OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Rafael Muñoz Salinas.
-********************************/
-#ifndef _ARUCO_MarkerDetector_H
-#define _ARUCO_MarkerDetector_H
-#include <opencv2/core.hpp>
-#include <cstdio>
-#include <iostream>
-#include "cameraparameters.h"
-#include "exports.h"
-#include "marker.h"
-
-namespace aruco
-{
-using namespace std;
-/**\brief Main class for marker detection
- *
- */
-class ARUCO_EXPORTS MarkerDetector
-{
- //Represent a candidate to be a maker
- class MarkerCandidate: public Marker{
- public:
- MarkerCandidate(){}
- MarkerCandidate(const Marker &M): Marker(M){}
- MarkerCandidate(const MarkerCandidate &M): Marker(M){
- contour=M.contour;
- idx=M.idx;
- }
- MarkerCandidate & operator=(const MarkerCandidate &M){
- *(Marker*)this = M;
- contour=M.contour;
- idx=M.idx;
- return *this;
- }
-
- vector<cv::Point> contour;//all the points of its contour
- int idx;//index position in the global contour list
- };
-public:
-
- /**
- * See
- */
- MarkerDetector();
-
- /**
- */
- ~MarkerDetector();
-
- /**Detects the markers in the image passed
- *
- * If you provide information about the camera parameters and the size of the marker, then, the extrinsics of the markers are detected
- *
- * @param input input color image
- * @param detectedMarkers output vector with the markers detected
- * @param camMatrix intrinsic camera information.
- * @param distCoeff camera distorsion coefficient. If set Mat() if is assumed no camera distorion
- * @param markerSizeMeters size of the marker sides expressed in meters
- * @param setYPerperdicular If set the Y axis will be perpendicular to the surface. Otherwise, it will be the Z axis
- */
- void detect(const cv::Mat &input,std::vector<Marker> &detectedMarkers,cv::Mat camMatrix=cv::Mat(),cv::Mat distCoeff=cv::Mat(),float markerSizeMeters=-1,bool setYPerperdicular=true) noexcept;
- /**Detects the markers in the image passed
- *
- * If you provide information about the camera parameters and the size of the marker, then, the extrinsics of the markers are detected
- *
- * @param input input color image
- * @param detectedMarkers output vector with the markers detected
- * @param camParams Camera parameters
- * @param markerSizeMeters size of the marker sides expressed in meters
- * @param setYPerperdicular If set the Y axis will be perpendicular to the surface. Otherwise, it will be the Z axis
- */
- void detect(const cv::Mat &input,std::vector<Marker> &detectedMarkers, CameraParameters camParams,float markerSizeMeters=-1,bool setYPerperdicular=true) noexcept;
-
- /**This set the type of thresholding methods available
- */
-
- enum ThresholdMethods {FIXED_THRES,ADPT_THRES,CANNY};
-
-
-
- /**Sets the threshold method
- */
- void setThresholdMethod(ThresholdMethods m) {
- _thresMethod=m;
- }
- /**Returns the current threshold method
- */
- ThresholdMethods getThresholdMethod()const {
- return _thresMethod;
- }
- /**
- * Set the parameters of the threshold method
- * We are currently using the Adptive threshold ee opencv doc of adaptiveThreshold for more info
- * @param param1: blockSize of the pixel neighborhood that is used to calculate a threshold value for the pixel
- * @param param2: The constant subtracted from the mean or weighted mean
- */
- void setThresholdParams(double param1,double param2) {
- _thresParam1=param1;
- _thresParam2=param2;
- }
- /**
- * Set the parameters of the threshold method
- * We are currently using the Adptive threshold ee opencv doc of adaptiveThreshold for more info
- * param1: blockSize of the pixel neighborhood that is used to calculate a threshold value for the pixel
- * param2: The constant subtracted from the mean or weighted mean
- */
- void getThresholdParams(double &param1,double &param2)const {
- param1=_thresParam1;
- param2=_thresParam2;
- }
-
-
- /**Returns a reference to the internal image thresholded. It is for visualization purposes and to adjust manually
- * the parameters
- */
- const cv::Mat & getThresholdedImage() {
- return thres;
- }
- /**Methods for corner refinement
- */
- enum CornerRefinementMethod {NONE,HARRIS,SUBPIX,LINES};
- /**
- */
- void setCornerRefinementMethod(CornerRefinementMethod method) {
- _cornerMethod=method;
- }
- /**
- */
- CornerRefinementMethod getCornerRefinementMethod()const {
- return _cornerMethod;
- }
- /**Specifies the min and max sizes of the markers as a fraction of the image size. By size we mean the maximum
- * of cols and rows.
- * @param min size of the contour to consider a possible marker as valid (0,1]
- * @param max size of the contour to consider a possible marker as valid [0,1)
- *
- */
- void setMinMaxSize(float min=0.03,float max=0.5)noexcept;
-
- /**reads the min and max sizes employed
- * @param min output size of the contour to consider a possible marker as valid (0,1]
- * @param max output size of the contour to consider a possible marker as valid [0,1)
- *
- */
- void getMinMaxSize(float &min,float &max){min=_minSize;max=_maxSize;}
-
- /**Enables/Disables erosion process that is REQUIRED for chessboard like boards.
- * By default, this property is enabled
- */
- void enableErosion(bool enable){_doErosion=enable;}
-
- /**
- * Specifies a value to indicate the required speed for the internal processes. If you need maximum speed (at the cost of a lower detection rate),
- * use the value 3, If you rather a more precise and slow detection, set it to 0.
- *
- * Actually, the main differences are that in highspeed mode, we employ setCornerRefinementMethod(NONE) and internally, we use a small canonical
- * image to detect the marker. In low speed mode, we use setCornerRefinementMethod(HARRIS) and a bigger size for the canonical marker image
- */
- void setDesiredSpeed(int val);
- /**
- */
- int getDesiredSpeed()const {
- return _speed;
- }
-
- /**
- * Allows to specify the function that identifies a marker. Therefore, you can create your own type of markers different from these
- * employed by default in the library.
- * The marker function must have the following structure:
- *
- * int myMarkerIdentifier(const cv::Mat &in,int &nRotations);
- *
- * The marker function receives the image 'in' with the region that migh contain one of your markers. These are the rectangular regions with black
- * in the image.
- *
- * As output your marker function must indicate the following information. First, the output parameter nRotations must indicate how many times the marker
- * must be rotated clockwise 90 deg to be in its ideal position. (The way you would see it when you print it). This is employed to know
- * always which is the corner that acts as reference system. Second, the function must return -1 if the image does not contains one of your markers, and its id otherwise.
- *
- */
- void setMakerDetectorFunction(int (* markerdetector_func)(const cv::Mat &in,int &nRotations) ) {
- markerIdDetector_ptrfunc=markerdetector_func;
- }
-
- /** Use an smaller version of the input image for marker detection.
- * If your marker is small enough, you can employ an smaller image to perform the detection without noticeable reduction in the precision.
- * Internally, we are performing a pyrdown operation
- *
- * @param level number of times the image size is divided by 2. Internally, we are performing a pyrdown.
- */
- void pyrDown(unsigned int level){pyrdown_level=level;}
-
- ///-------------------------------------------------
- /// Methods you may not need
- /// Thesde methods do the hard work. They have been set public in case you want to do customizations
- ///-------------------------------------------------
-
- /**
- * Thesholds the passed image with the specified method.
- */
- void thresHold(int method,const cv::Mat &grey,cv::Mat &thresImg,double param1=-1,double param2=-1)noexcept;
- /**
- * Detection of candidates to be markers, i.e., rectangles.
- * This function returns in candidates all the rectangles found in a thresolded image
- */
- void detectRectangles(const cv::Mat &thresImg,vector<std::vector<cv::Point2f> > & candidates);
-
- /**Returns a list candidates to be markers (rectangles), for which no valid id was found after calling detectRectangles
- */
- const vector<std::vector<cv::Point2f> > &getCandidates() {
- return _candidates;
- }
-
- /**Given the iput image with markers, creates an output image with it in the canonical position
- * @param in input image
- * @param out image with the marker
- * @param size of out
- * @param points 4 corners of the marker in the image in
- * @return true if the operation succeed
- */
- bool warp(cv::Mat &in,cv::Mat &out,cv::Size size, std::vector<cv::Point2f> points)noexcept;
-
-
-
- /** Refine MarkerCandidate Corner using LINES method
- * @param candidate candidate to refine corners
- */
- void refineCandidateLines(MarkerCandidate &candidate);
-
-
- /**DEPRECATED!!! Use the member function in CameraParameters
- *
- * Given the intrinsic camera parameters returns the GL_PROJECTION matrix for opengl.
- * PLease NOTE that when using OpenGL, it is assumed no camera distorsion! So, if it is not true, you should have
- * undistor image
- *
- * @param CamMatrix arameters of the camera specified.
- * @param orgImgSize size of the original image
- * @param size of the image/window where to render (can be different from the real camera image). Please not that it must be related to CamMatrix
- * @param proj_matrix output projection matrix to give to opengl
- * @param gnear,gfar: visible rendering range
- * @param invert: indicates if the output projection matrix has to yield a horizontally inverted image because image data has not been stored in the order of glDrawPixels: bottom-to-top.
- */
- static void glGetProjectionMatrix( CameraParameters & CamMatrix,cv::Size orgImgSize, cv::Size size,double proj_matrix[16],double gnear,double gfar,bool invert=false )noexcept;
-
-private:
-
- bool _enableCylinderWarp;
- bool warp_cylinder ( cv::Mat &in,cv::Mat &out,cv::Size size, MarkerCandidate& mc ) noexcept;
- /**
- * Detection of candidates to be markers, i.e., rectangles.
- * This function returns in candidates all the rectangles found in a thresolded image
- */
- void detectRectangles(const cv::Mat &thresImg,vector<MarkerCandidate> & candidates);
-public:
- std::vector<std::vector<cv::Point>> contours;
- //Current threshold method
- ThresholdMethods _thresMethod;
- //Threshold parameters
- double _thresParam1,_thresParam2;
- //Current corner method
- CornerRefinementMethod _cornerMethod;
- //minimum and maximum size of a contour lenght
- float _minSize,_maxSize;
- //Speed control
- int _speed;
- int _markerWarpSize;
- bool _doErosion;
- //vectr of candidates to be markers. This is a vector with a set of rectangles that have no valid id
- vector<std::vector<cv::Point2f> > _candidates;
- //level of image reduction
- int pyrdown_level;
- //Images
- cv::Mat grey,thres,thres2,reduced;
- //pointer to the function that analizes a rectangular region so as to detect its internal marker
- int (* markerIdDetector_ptrfunc)(const cv::Mat &in,int &nRotations);
-private:
- /**
- */
- bool isInto(cv::Mat &contour,std::vector<cv::Point2f> &b);
- /**
- */
- int perimeter(std::vector<cv::Point2f> &a);
-
-
-// //GL routines
-//
-// static void argConvGLcpara2( double cparam[3][4], int width, int height, double gnear, double gfar, double m[16], bool invert )noexcept;
-// static int arParamDecompMat( double source[3][4], double cpara[3][4], double trans[3][4] )noexcept;
-// static double norm( double a, double b, double c );
-// static double dot( double a1, double a2, double a3,
-// double b1, double b2, double b3 );
-//
-
- //detection of the
- void findBestCornerInRegion_harris(const cv::Mat & grey,vector<cv::Point2f> & Corners,int blockSize);
-
-
- // auxiliar functions to perform LINES refinement
- void interpolate2Dline( const vector< cv::Point > &inPoints, cv::Point3f &outLine);
- cv::Point2f getCrossPoint(const cv::Point3f& line1, const cv::Point3f& line2);
-
-
- /**Given a vector vinout with elements and a boolean vector indicating the lements from it to remove,
- * this function remove the elements
- * @param vinout
- * @param toRemove
- */
- template<typename T>
- void removeElements(vector<T> & vinout,const vector<bool> &toRemove)
- {
- //remove the invalid ones by setting the valid in the positions left by the invalids
- size_t indexValid=0;
- for (size_t i=0;i<toRemove.size();i++) {
- if (!toRemove[i]) {
- if (indexValid!=i) vinout[indexValid]=vinout[i];
- indexValid++;
- }
- }
- vinout.resize(indexValid);
- }
-
- //graphical debug
- void drawApproxCurve(cv::Mat &in,std::vector<cv::Point> &approxCurve ,cv::Scalar color);
- void drawContour(cv::Mat &in,std::vector<cv::Point> &contour,cv::Scalar );
- void drawAllContours(cv::Mat input, std::vector<std::vector<cv::Point> > &contours);
- void draw(cv::Mat out,const std::vector<Marker> &markers );
-
-};
-
-
-
-
-}
-#endif
diff --git a/tracker-aruco/lang/nl_NL.ts b/tracker-aruco/lang/nl_NL.ts
index 2f951e65..e5a94f3b 100644
--- a/tracker-aruco/lang/nl_NL.ts
+++ b/tracker-aruco/lang/nl_NL.ts
@@ -36,30 +36,6 @@
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Lees de &lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Aruco-tracker&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;wiki-pagina&lt;/span&gt;&lt;/a&gt; en voornamelijk de laatste paragraaf voordat u de markering uitprint.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
- <source>Default</source>
- <translation>Standaard</translation>
- </message>
- <message>
- <source>30</source>
- <translation>30</translation>
- </message>
- <message>
- <source>60</source>
- <translation>30</translation>
- </message>
- <message>
- <source>75</source>
- <translation>75</translation>
- </message>
- <message>
- <source>125</source>
- <translation>125</translation>
- </message>
- <message>
- <source>200</source>
- <translation>200</translation>
- </message>
- <message>
<source>Default (not recommended!)</source>
<translation>Standaard (niet aanbevolen)</translation>
</message>
@@ -68,10 +44,6 @@
<translation>Camera-instellingen</translation>
</message>
<message>
- <source>Model rotation</source>
- <translation>Modelrotatie</translation>
- </message>
- <message>
<source>Head X</source>
<translation>Hoofd-X</translation>
</message>
@@ -87,5 +59,24 @@
<source>Toggle calibration</source>
<translation>Schakel tussen kalibratie</translation>
</message>
+ <message>
+ <source>1280x720</source>
+ <translation>1280x720</translation>
+ </message>
+ <message>
+ <source>MJPEG</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>1920x1080</source>
+ <translation type="unfinished">1280x1080 {1920x?}</translation>
+ </message>
+</context>
+<context>
+ <name>aruco_dialog</name>
+ <message>
+ <source>Default</source>
+ <translation type="unfinished">Standaard</translation>
+ </message>
</context>
</TS>
diff --git a/tracker-aruco/lang/ru_RU.ts b/tracker-aruco/lang/ru_RU.ts
index 1818fb13..639b59ce 100644
--- a/tracker-aruco/lang/ru_RU.ts
+++ b/tracker-aruco/lang/ru_RU.ts
@@ -16,75 +16,66 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Default</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>30</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>60</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>75</source>
+ <source>640x480</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>125</source>
+ <source>320x240</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>200</source>
+ <source>Default (not recommended!)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>640x480</source>
+ <source>Camera settings</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>320x240</source>
+ <source>Frames per second</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Default (not recommended!)</source>
+ <source>Camera name</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Camera settings</source>
+ <source>Resolution</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Frames per second</source>
+ <source>Head X</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Camera name</source>
+ <source>Head Y</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Resolution</source>
+ <source>Head Z </source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Model rotation</source>
+ <source>Toggle calibration</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Head X</source>
+ <source>1280x720</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Head Y</source>
+ <source>MJPEG</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Head Z </source>
+ <source>1920x1080</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>aruco_dialog</name>
<message>
- <source>Toggle calibration</source>
+ <source>Default</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-aruco/lang/stub.ts b/tracker-aruco/lang/stub.ts
index 055d9d7d..f213eb41 100644
--- a/tracker-aruco/lang/stub.ts
+++ b/tracker-aruco/lang/stub.ts
@@ -16,75 +16,66 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Default</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>30</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>60</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>75</source>
+ <source>640x480</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>125</source>
+ <source>320x240</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>200</source>
+ <source>Default (not recommended!)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>640x480</source>
+ <source>Camera settings</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>320x240</source>
+ <source>Frames per second</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Default (not recommended!)</source>
+ <source>Camera name</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Camera settings</source>
+ <source>Resolution</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Frames per second</source>
+ <source>Head X</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Camera name</source>
+ <source>Head Y</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Resolution</source>
+ <source>Head Z </source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Model rotation</source>
+ <source>Toggle calibration</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Head X</source>
+ <source>1280x720</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Head Y</source>
+ <source>MJPEG</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Head Z </source>
+ <source>1920x1080</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>aruco_dialog</name>
<message>
- <source>Toggle calibration</source>
+ <source>Default</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-aruco/lang/zh_CN.ts b/tracker-aruco/lang/zh_CN.ts
index 1967ee70..5d5646b7 100644
--- a/tracker-aruco/lang/zh_CN.ts
+++ b/tracker-aruco/lang/zh_CN.ts
@@ -1,90 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>Form</name>
<message>
<source>Tracker settings</source>
- <translation type="unfinished"></translation>
+ <translation>追踪器设置</translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Read the &lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Aruco-tracker&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;wiki page&lt;/span&gt;&lt;/a&gt; and especially the last paragraph before printing markers.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
- <translation type="unfinished"></translation>
+ <translation type="unfinished">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;阅读此&lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Aruco-tracker&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0077DD;&quot;&gt;Wiki页é¢&lt;/span&gt;&lt;/a&gt; ç‰¹åˆ«æ˜¯æ‰“å°æ ‡è®°å‰çš„æœ€åŽä¸€æ®µ. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Diagonal FOV</source>
- <translation type="unfinished"></translation>
+ <translation>对角FOV</translation>
</message>
<message>
<source>640x480</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>320x240</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>Default (not recommended!)</source>
- <translation type="unfinished"></translation>
+ <translation>默认 (䏿ލè!)</translation>
</message>
<message>
- <source>Default</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>30</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>60</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>75</source>
- <translation type="unfinished"></translation>
+ <source>Camera name</source>
+ <translation>相机åç§°</translation>
</message>
<message>
- <source>125</source>
- <translation type="unfinished"></translation>
+ <source>Frames per second</source>
+ <translation>FPS</translation>
</message>
<message>
- <source>200</source>
- <translation type="unfinished"></translation>
+ <source>Resolution</source>
+ <translation type="unfinished">分辨率</translation>
</message>
<message>
- <source>Camera name</source>
- <translation type="unfinished"></translation>
+ <source>Camera settings</source>
+ <translation>相机设置</translation>
</message>
<message>
- <source>Frames per second</source>
+ <source>Head X</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Resolution</source>
+ <source>Head Y</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Camera settings</source>
+ <source>Head Z </source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Model rotation</source>
- <translation type="unfinished"></translation>
+ <source>Toggle calibration</source>
+ <translation>åˆ‡æ¢æ ¡å‡†</translation>
</message>
<message>
- <source>Head X</source>
- <translation type="unfinished"></translation>
+ <source>1280x720</source>
+ <translation></translation>
</message>
<message>
- <source>Head Y</source>
+ <source>MJPEG</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Head Z </source>
- <translation type="unfinished"></translation>
+ <source>1920x1080</source>
+ <translation></translation>
</message>
+</context>
+<context>
+ <name>aruco_dialog</name>
<message>
- <source>Toggle calibration</source>
+ <source>Default</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-easy/CMakeLists.txt b/tracker-easy/CMakeLists.txt
new file mode 100644
index 00000000..ff537877
--- /dev/null
+++ b/tracker-easy/CMakeLists.txt
@@ -0,0 +1,25 @@
+include(opentrack-opencv)
+find_package(OpenCV QUIET)
+
+if(OpenCV_FOUND)
+ try_compile(tracker-easy_ocv-check "${CMAKE_CURRENT_BINARY_DIR}"
+ SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/ocv-check.cxx"
+ CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${OpenCV_INCLUDE_DIRS}"
+ "-DCXX_STANDARD=20" "-DCXX_STANDARD_REQUIRED=1"
+ OUTPUT_VARIABLE krap)
+ if(tracker-easy_ocv-check)
+ foreach(k highgui videoio imgcodecs imgproc calib3d video features2d flann)
+ otr_install_lib("opencv_${k}" "${opentrack-libexec}")
+ endforeach()
+
+ if(CMAKE_COMPILER_IS_GNUCXX)
+ add_compile_options(-Wno-sign-compare)
+ elseif(MSVC)
+ add_compile_options(-wd4018)
+ endif()
+
+ otr_module(tracker-easy)
+ target_include_directories(${self} SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS})
+ target_link_libraries(${self} opencv_core opencv_imgproc opencv_calib3d opencv_video opencv_highgui opentrack-cv opentrack-video)
+ endif()
+endif()
diff --git a/tracker-easy/Resources/cap_front.png b/tracker-easy/Resources/cap_front.png
new file mode 100644
index 00000000..14207a67
--- /dev/null
+++ b/tracker-easy/Resources/cap_front.png
Binary files differ
diff --git a/tracker-easy/Resources/cap_side.png b/tracker-easy/Resources/cap_side.png
new file mode 100644
index 00000000..5ad4ee65
--- /dev/null
+++ b/tracker-easy/Resources/cap_side.png
Binary files differ
diff --git a/tracker-easy/Resources/clip_front.png b/tracker-easy/Resources/clip_front.png
new file mode 100644
index 00000000..04880138
--- /dev/null
+++ b/tracker-easy/Resources/clip_front.png
Binary files differ
diff --git a/tracker-easy/Resources/clip_side.png b/tracker-easy/Resources/clip_side.png
new file mode 100644
index 00000000..72667ac7
--- /dev/null
+++ b/tracker-easy/Resources/clip_side.png
Binary files differ
diff --git a/tracker-easy/Resources/easy-tracker-logo.png b/tracker-easy/Resources/easy-tracker-logo.png
new file mode 100644
index 00000000..5a7da41c
--- /dev/null
+++ b/tracker-easy/Resources/easy-tracker-logo.png
Binary files differ
diff --git a/tracker-easy/doc/index.htm b/tracker-easy/doc/index.htm
new file mode 100644
index 00000000..87b7356f
--- /dev/null
+++ b/tracker-easy/doc/index.htm
@@ -0,0 +1,262 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+ <title>FTNoIR PointTracker Help</title>
+
+ <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+
+ <meta name="author"
+ content="Patrick Ruoff (C14)"/>
+ <meta name="keywords"
+ content="facetracknoir infrared point model tracker plugin"/>
+ <meta name="description"
+ content="Pointtracker plugin for FaceTrackNoIR"/>
+
+ <link rel="shortcut icon" href="ptrack.ico" type="image/vnd.microsoft.icon" />
+ <link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+<div id="navbar">
+<ul class="navbar">
+<li class="navbar"><a class="navbar" href="#about">About</a></li>
+<li class="navbar"><a class="navbar" href="#settings">Settings</a></li>
+<li class="navbar"><a class="navbar" href="#setup">Filter Setup</a></li>
+<li class="navbar"><a class="navbar" href="#support">Support</a></li>
+<li class="navbar"><a class="navbar" href="#changelog">ChangeLog</a></li>
+<li class="navbar"><a class="navbar" href="#build_instructions">Build Instructions</a></li>
+</ul>
+</div>
+
+<div id="content">
+<div style="text-align:center"><h1>FaceTrackNoIR PointTracker Plugin</h1><img src="logo.png" alt="PointTracker Plugin Logo" /></div>
+
+<a class="nav" id="about"></a>
+<h2>About</h2>
+<div class="indent">
+<p>
+PointTracker is a plugin for the free head tracking software <a href="http://facetracknoir.sourceforge.net">FaceTrackNoIR</a>
+which introduces the capability to track a (typically IR-) point model comprising 3 bright points to FaceTrackNoIR,
+much like the popular free tracking software <a href="http://www.free-track.net/">Freetrack</a> does.<br/>
+It was created as a stable modular alternative to Freetrack, which has some stability issues with newer systems and seems to be no longer actively developped.
+</p>
+</div>
+
+<a class="nav" id="settings"></a>
+<h2>Settings</h2>
+<div class="indent">
+<p>
+This section desribes the various settings of the PointTracker plugin in detail.
+</p>
+
+<img src="settings1.png" alt="Settings Pane 1"/>
+<dl>
+<dt>Show VideoWidget</dt><dd>Whether the video widget is updated or not. It may save some performance to turn this off when not needed</dd>
+<dt>Sleep time</dt><dd>Time the tracking thread sleeps after each processed image. It's inverse should be below the framefrate you want to achieve.
+(check the framerate in the status region when tracker is active, in case the sleep time is too high, the framerate will decrease).
+Low values will result in more CPU-load.</dd>
+<dt>Dynamic Pose Resolution</dt><dd>Whether the point correspondence and pose ambiquity is resolved using a more sophisticated dynamic algorithm (constant velocity prediction) or a simple static resolution.
+Dynamic pose resolution can capture more extreme poses but may occasionally get stuck in a wrong pose estimates so that a reset of the internal state becomes neccessary.</dd>
+<dt>Auto-reset time</dt><dd>If no valid tracking result can be found when using dynamic pose resolution, the tracker will automatically reset its internal state (used for resolving the pose ambiguity and point correspondence)
+and return to a fail-safe initialization phase that assumes a neutral pose after this time.
+Decrease this time, if you get stuck in a wrong pose too often.</dd>
+<dt>Reset</dt><dd>Manually reset the trackers internal state used for dynamic pose resolution and return to a fail-safe initialization phase that assumes a neutral pose.
+You may use this in case you get stuck in a wrong pose.</dd>
+<dt>Enable Axis ...</dt><dd>Which axis to use for FTNoIR.</dd>
+</dl>
+
+<img src="settings2.png" alt="Settings Pane 2"/>
+<dl>
+<dt>Device</dt><dd>The camera used for tracking.</dd>
+<dt>Resolution</dt><dd>The desired capture resolution. If your camera does not support the entered resolution the true output resolution may be different or even invalid.
+You may check the true capture resolution in the status area while the tracker is running. A higher resolution results in more accurate point positions and will increase the
+stability of the tracking result, as long as the signal/noise ratio is sufficiently high.</dd>
+<dt>FPS</dt><dd>The desired capture framerate. Again, if your camera does not support the entered framerate, the true caputre framerate may be different or invalid.
+You may check the true processing framerate in the status area while the tracker is running.</dd>
+<dt>F/W</dt><dd>The focal length of the camera divided by the sensor width (of course in the same units).
+In case you don't have access to your camera's specifications, you can measure this yourself by placing a plane object of known width (for example a piece of cardboard) in front of the camera until it fills the whole image width.
+Then measure the distance between the object and the camera and divide by the object width.</dd>
+<dt>VideoWidget</dt><dd>Shows a resizable stand-alone video widget that shows the same content as the integrated video widget in FTNoIR.
+Update rate is only 10 fps and may lag behind a bit. Mainly useful during calibration of the point extraction. Same as for the integrated wiget, to save resources, this widget should only be shown when needed.</dd>
+<dt>Roll Pitch Yaw...</dt><dd>The orientation of the camera relative to the reference frame.
+If these angles are setup properly, the direction of translations may not be correct.
+Roll is treated in a special way since it is implemented as a frame rotation by +/- 90 deg that is transparent to the rest of the processing pipeline.
+</dd>
+<dt>Threshold</dt><dd>The threshold for point recognition. Areas above the threshold are shown in blue in the VideoWidget.
+Since point accuracy is best if the points are as big as possible in pixels, the theshold should be chosen as low as possible (stop before the contour of the points becomes "noisy").
+If small reflections are being falsely classified as points, increasing the minimum point diameter (see below) may help.</dd>
+<dt>Min Diameter</dt><dd>Minimum diameter of blobs to be classified as a pointmodel-point.</dd>
+<dt>Max Diameter</dt><dd>Maximum diameter of blobs to be classified as a pointmodel-point.</dd>
+<dt>Status</dt><dd>The tracker's status is shown in this area while the tracker is running.
+The FPS shown here correspond to the framerate of the whole tracker processing chain and may be lower than what your camera is able to provide, when<br/>
+1. The processing gets not enough CPU time<br/>
+2. The sleep time of the tracking thread is set too high<br/></dd>
+</dl>
+
+<img src="settings3.png" alt="Settings Pane 3"/>
+<dl>
+<dt>Model Selection and Dimensions ...</dt><dd>
+First select your model type (point, clip, custom), then enter the dimensions of your model in milimeters here.<br/>
+For the custom setting, the coordinates of the two remaining model points have to be entered (reference point M0 is at (0,0,0)) in a pose where the model roughly faces the camera.
+For orientation, the coordinates for the standard Freetrack clip are (0,40,-30), (0,-70,-80) and the ones for the cap (40,-60,-100), (-40,-60,-100).<br/>
+When using a custom point-model configuration, the following restrictions should be observed:<br/>
+The plane in which the 3 points lie should never be parallel to the image plane, M0-M1 and M0-M2 should be roughly perpendicular.</dd>
+
+<dt>Model Position</dt><dd>The vector from the model to the center of the head in the model frame. Can be calibrated automatically.</dd>
+<dt>Calibrate</dt><dd>In order to automatically calibrate the model-head offset, do the following:<br/>Press the Calibrate button, then look around while not moving your shoulder. (i.e. only rotation, no translation).
+Do not stay in one pose for too long. The current translation estimate will be updated in real time. As soon as the values stabilized sufficiently, press the Calibrate button again to stop the calibration process.</dd>
+</dl>
+</div>
+
+<a class="nav" id="setup"></a>
+<h2>Filter Setup</h2>
+<div class="indent">
+<p>
+This section desribes how the FTNoIR filter work and what the recommended settings for PointTracker are.
+</p>
+<p>
+Filtering is always a tradeoff between stability, accuracy and responsiveness.
+</p>
+<p>
+The <q>Smoothing</q> filter in FTNoIR is just a simple average over the last n samples.
+Since this filter produces input lag no matter how fast the head-movements are, it is recommended to turn it off by setting samples to 1.
+</p>
+<p>
+In the filter tab, it is recommended to select <q>Accela Filter Mk2</q>.
+Accela is a non-linear filter that works as follows:<br/>
+It looks at the difference between the new raw values <i>new_val</i> from the tracker and the last filtered value <i>old_val</i>
+and maps this difference via the customizable response function <i>f</i> via:<br/>
+</p>
+<p style="text-align: center">
+<i>new_val = old_val + f(new_val - old_val) / reduction_factor</i>
+</p>
+<p>
+So by setting <i>f(x) = reduction_factor * x</i>, one will get no filtering at all.<br/>
+If you set lower values for small x, small deviations (usually noise) will get dampened.
+This results in a dynamic dead-zone around the current position.
+</p>
+<p>
+The last two points are used by accela to extrapolate for large deviations.
+So in order to get a fast unfiltered response for large deviations, the line connecting the last two points should have a slope >= <i>reduction_factor</i>.
+</p>
+<p>
+More aggressive accela settings than the default FTNoIR accela settings are recommended in order to decrease the filtering lag and fully use the potential of point tracking.<br/>
+My current settings are:
+</p>
+<pre class="indent"><code>
+[Accela]
+Reduction=20
+
+[Curves-Accela-Scaling-Rotation]
+point-count=4
+point-0-x=0.1
+point-0-y=0
+point-1-x=1.43
+point-1-y=2.45
+point-2-x=2.0
+point-2-y=5.44
+point-3-x=2.06
+point-3-y=6
+</code></pre>
+<p>
+The curve is not too different from the standard one (except that I like a small dynamic dead zone for steady aiming, that's why the curve has a slope of 0 at the beginning).<br/>
+However, the reduction factor is decreased to a value of 20 (compared to the standard value of 100). This implies that each value of the curve is effectively 5 times higher than in standard FTNoIR (see formula above), which means higher responsiveness but can also lead to jitter/shaking.<br/>
+Keep in mind that there are no <q>best filter settings</q>. Since filtering is always a compromise it's a matter of personal taste and
+playing around with the filter settings is highly recommended.
+</p>
+</div>
+
+<a class="nav" id="support"></a>
+<h2>Support</h2>
+<div class="indent">
+<p>
+For questions/feedback about the plugin, post to the <a href="https://sourceforge.net/projects/facetracknoir/forums">FTNoIR-Forum</a>.<br/>
+In case you like this plugin and would like to support the author, you may consider making a donation.
+</p>
+<div style="text-align:center">
+<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
+<fieldset class="blind">
+<input type="hidden" name="cmd" value="_s-xclick"/>
+<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHJwYJKoZIhvcNAQcEoIIHGDCCBxQCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYCa+2zPZ+6vFPqveJsBIjFLpy54m7tl0AdojRr/K5qa3QJDyRBhGwGAP2jRihkmZFE2oKlfLpkz7nrwOQY/wFEPkggO+cABxUfjcQVpIupHEtwdV0hMklLs0RmACJy802yfi1yTiCpJ4hvWN+VfUI3gOiZ9uRZ3L4iGXES7xtqJbDELMAkGBSsOAwIaBQAwgaQGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIeopHzcJ8XBOAgYCYJFyTejSplEOwF21aQ01qQOads9Z+RUVI+hlvM/pHTjimaZPKSis3poAeqv6wKn40DpLNxDnmcT+Y9KXhrV+Gy4GZCPaeNzq2vquQ2ZVN0fTr84QVmKqPkjMBGmJAHSLCcZswUddemJgoD1uyvS0kNbchvxw7gDXJnJeBRNyXXKCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTEyMDkyMzA5NTcwOFowIwYJKoZIhvcNAQkEMRYEFG/qW7uo4R4m5uFYegcZaZsTPAcUMA0GCSqGSIb3DQEBAQUABIGAGygLfrR6IQbG2xZY2OrwKkfmRwiwtnXpLBnSbnWb7XxUOMhvM6962RiKBQBGP0+XYw0S9yu8ZHx7tqz/3bcMfGjtz7PwixYx6Rm8Z29ja78aUy5FmU7fc9yAWFxLHptSliK1dJBPxdQa9J2YSDvPQPAj+AdB9sJvqJoMoxTFGM4=-----END PKCS7-----
+"/>
+<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif" name="submit" alt="PayPal - The safer, easier way to pay online!"/>
+<img alt="" src="https://www.paypalobjects.com/de_DE/i/scr/pixel.gif" width="1" height="1"/>
+</fieldset>
+</form>
+</div>
+</div>
+
+<a class="nav" id="changelog"></a>
+<h2>ChangeLog</h2>
+<div class="indent">
+<h3>1.1</h3>
+<ul>
+<li>Added camera yaw and roll correction (intended for vertically mounted cameras)</li>
+<li>Improved point extraction algorithm, thanks to Michael Welter</li>
+<li>UI improvements: Select camera by device name, different VideoWidget architecture</li>
+<li>Bugfixes: Removed 99 FPS limitation</li>
+</ul>
+
+<h3>1.0</h3>
+<ul>
+<li>Added camera pitch correction</li>
+<li>Better communication with FTNoIR: output axis configuration, status report</li>
+</ul>
+
+<h3>1.0 beta</h3>
+<ul>
+<li>Switchted to videoInput library for capture. Desired capture resolution and fps can now be customized</li>
+<li>Introduced dynamic point-correspondence and POSIT-ambiguity resolution, which allows for the reconstruction of more extreme poses</li>
+<li>More convenient freetrack-like model dimension GUI</li>
+<li>Bugfixes: VideoWidget skipping frames, Timer resolution too low for accurate FPS measurement</li>
+</ul>
+</div>
+
+<a class="nav" id="build_instructions"></a>
+<h2>Build Instructions</h2>
+<div class="indent">
+<p>
+This section describes what you need to do in order to build PointTracker yourself.<br/>
+You can find the sources at the <a href="https://sourceforge.net/projects/ftnoirpt/">project site</a>
+or as part of the <a href="https://sourceforge.net/projects/facetracknoir/">FTNoIR sources</a>.
+</p>
+<p> The project was created with Visual Studio. </p>
+
+<h3>Dependencies</h3>
+<ul>
+<li>Qt 4.8.2 library</li>
+<li>Qt plugin for Visual studio</li>
+<li>OpenCV 2.4 prebuilt for Windows</li>
+<li>Boost 1.47</li>
+</ul>
+
+<h3>Details</h3>
+<div class="indent">
+<h4>Common</h4>
+<ul>
+<li>setup environment variable "QTDIR" (example value "D:\Devel\Libs\Qt\4.8.2")</li>
+<li>add "%QTDIR%\bin" to PATH</li>
+<li>setup environment variable "BOOST_DIR" (example value "D:\Devel\Libs\boost_1_47_0")</li>
+<li>setup environment variable "OPENCV_DIR" (example value "D:\Devel\Libs\opencv\build")</li>
+</ul>
+<h4>Debug</h4>
+<p>opencv linked dynamically:</p>
+<ul>
+<li>add "%OPENCV_DIR%\x86\vc9\bin" to PATH</li>
+</ul>
+<p>(in case of different Visual studio, change PATH and linker dependencies accordingly)</p>
+<h4>Release</h4>
+<p>opencv linked statically:</p>
+<ul>
+<li>custom build a statically linked version of opencv with the buil-option BUILD_WITH_STATIC_CRT set to OFF!</li>
+<li>copy resulting libaries to "%OPENCV_DIR%\x86\vc9\static_lib"</li>
+</ul>
+<p>(in case of different Visual studio, change PATH and linker dependencies accordingly)</p>
+</div>
+</div>
+
+</div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/tracker-easy/doc/logo.png b/tracker-easy/doc/logo.png
new file mode 100644
index 00000000..95032a25
--- /dev/null
+++ b/tracker-easy/doc/logo.png
Binary files differ
diff --git a/tracker-easy/doc/ptrack.ico b/tracker-easy/doc/ptrack.ico
new file mode 100644
index 00000000..c4b2aedc
--- /dev/null
+++ b/tracker-easy/doc/ptrack.ico
Binary files differ
diff --git a/tracker-easy/doc/settings1.png b/tracker-easy/doc/settings1.png
new file mode 100644
index 00000000..35b84c5c
--- /dev/null
+++ b/tracker-easy/doc/settings1.png
Binary files differ
diff --git a/tracker-easy/doc/settings2.png b/tracker-easy/doc/settings2.png
new file mode 100644
index 00000000..c6cfd1f3
--- /dev/null
+++ b/tracker-easy/doc/settings2.png
Binary files differ
diff --git a/tracker-easy/doc/settings3.png b/tracker-easy/doc/settings3.png
new file mode 100644
index 00000000..5922403d
--- /dev/null
+++ b/tracker-easy/doc/settings3.png
Binary files differ
diff --git a/tracker-easy/doc/style.css b/tracker-easy/doc/style.css
new file mode 100644
index 00000000..0c3d29a6
--- /dev/null
+++ b/tracker-easy/doc/style.css
@@ -0,0 +1,131 @@
+body {
+ width: 1000px;
+ font-size: 13px;
+ color: #000000;
+ padding: 0;
+ margin: 0 auto;
+ background: #444444;
+ font-family: verdana,arial;
+}
+
+table {
+ border-width: 3px;
+ border-color: #0000FF;
+ border-style: ridge;
+ margin-top: 5px;
+ background-color: #E0E0FF;
+}
+
+table.blind {
+ border: none;
+ background-color: #E6E6E6;
+}
+
+fieldset.blind {
+ border: none;
+}
+
+h1 { font-size: 160%; }
+h2 { font-size: 140%; }
+h3 { font-size: 115%; }
+
+.indent {
+ margin-left: 25px;
+}
+
+p
+{
+ margin-left: 10px;
+}
+
+li
+{
+ margin: 10px;
+}
+
+
+dl
+{
+ /*width: 80%;*/
+ border-bottom: 1px solid #999;
+}
+
+dt
+{
+ padding-top: 5px;
+ font-weight: bold;
+ border-top: 1px solid #999;
+}
+
+dd
+{
+ padding: 5px;
+}
+
+
+hr {
+ color: #688938;
+}
+
+a:link, a:visited {
+ color: #0000BF;
+}
+a:hover {
+ color: #0000FF;
+}
+
+a.nav {
+ position: relative;
+ top: -30px;
+ display: block;
+ visibility: hidden;
+}
+
+#navbar {
+ width: 1000px;
+ height: 30px;
+ background-color:#1a1a1b;
+ position: fixed;
+ margin: 0 auto;
+ padding: 0;
+}
+
+#navbar ul
+{
+ list-style-type: none;
+ margin: 0 auto;
+ padding: 0;
+ overflow: hidden;
+}
+
+#navbar li
+{
+ margin: 0 auto;
+ padding: 5px;
+ float:left;
+}
+
+#navbar a:link,a:visited
+{
+ display:block;
+ width:150px;
+ font-weight:bold;
+ color:#e85d02;
+ text-align:center;
+ /*padding:4px;*/
+ text-decoration:none;
+ /*text-transform:uppercase;*/
+}
+
+#navbar a:hover,a:active
+{
+ color:#ffffff;
+}
+
+#content {
+ background-color:#ffffff;
+ padding: 15px;
+ padding-top: 40px;
+ padding-right: 40px;
+ margin: 0 auto;
+}
diff --git a/tracker-easy/export.hpp b/tracker-easy/export.hpp
new file mode 100644
index 00000000..a733c9fe
--- /dev/null
+++ b/tracker-easy/export.hpp
@@ -0,0 +1,11 @@
+// generates export.hpp for each module from compat/linkage.hpp
+
+#pragma once
+
+#include "compat/linkage-macros.hpp"
+
+#ifdef BUILD_TRACKER_PT
+# define OTR_PT_EXPORT OTR_GENERIC_EXPORT
+#else
+# define OTR_PT_EXPORT OTR_GENERIC_IMPORT
+#endif
diff --git a/tracker-easy/kalman-filter-pose.cpp b/tracker-easy/kalman-filter-pose.cpp
new file mode 100644
index 00000000..42346bb5
--- /dev/null
+++ b/tracker-easy/kalman-filter-pose.cpp
@@ -0,0 +1,118 @@
+/* Copyright (c) 2019, Stephane Lenclud <github@lenclud.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 "kalman-filter-pose.h"
+
+namespace EasyTracker
+{
+
+ KalmanFilterPose::KalmanFilterPose()
+ {
+
+ }
+
+
+ void KalmanFilterPose::Init(int nStates, int nMeasurements, int nInputs, double dt)
+ {
+ iMeasurements = cv::Mat(nMeasurements, 1, CV_64FC1);
+
+ init(nStates, nMeasurements, nInputs, CV_64F); // init Kalman Filter
+
+ // TODO: Use parameters instead of magic numbers
+ setIdentity(processNoiseCov, cv::Scalar::all(1)); //1e-5 // set process noise
+ setIdentity(measurementNoiseCov, cv::Scalar::all(1)); //1e-2 // set measurement noise
+ setIdentity(errorCovPost, cv::Scalar::all(1)); // error covariance
+
+ /** DYNAMIC MODEL **/
+
+ // [1 0 0 dt 0 0 dt2 0 0 0 0 0 0 0 0 0 0 0]
+ // [0 1 0 0 dt 0 0 dt2 0 0 0 0 0 0 0 0 0 0]
+ // [0 0 1 0 0 dt 0 0 dt2 0 0 0 0 0 0 0 0 0]
+ // [0 0 0 1 0 0 dt 0 0 0 0 0 0 0 0 0 0 0]
+ // [0 0 0 0 1 0 0 dt 0 0 0 0 0 0 0 0 0 0]
+ // [0 0 0 0 0 1 0 0 dt 0 0 0 0 0 0 0 0 0]
+ // [0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0]
+ // [0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0]
+ // [0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]
+ // [0 0 0 0 0 0 0 0 0 1 0 0 dt 0 0 dt2 0 0]
+ // [0 0 0 0 0 0 0 0 0 0 1 0 0 dt 0 0 dt2 0]
+ // [0 0 0 0 0 0 0 0 0 0 0 1 0 0 dt 0 0 dt2]
+ // [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 dt 0 0]
+ // [0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 dt 0]
+ // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 dt]
+ // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0]
+ // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0]
+ // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1]
+
+ // position
+ transitionMatrix.at<double>(0, 3) = dt;
+ transitionMatrix.at<double>(1, 4) = dt;
+ transitionMatrix.at<double>(2, 5) = dt;
+ transitionMatrix.at<double>(3, 6) = dt;
+ transitionMatrix.at<double>(4, 7) = dt;
+ transitionMatrix.at<double>(5, 8) = dt;
+ transitionMatrix.at<double>(0, 6) = 0.5*pow(dt, 2);
+ transitionMatrix.at<double>(1, 7) = 0.5*pow(dt, 2);
+ transitionMatrix.at<double>(2, 8) = 0.5*pow(dt, 2);
+
+ // orientation
+ transitionMatrix.at<double>(9, 12) = dt;
+ transitionMatrix.at<double>(10, 13) = dt;
+ transitionMatrix.at<double>(11, 14) = dt;
+ transitionMatrix.at<double>(12, 15) = dt;
+ transitionMatrix.at<double>(13, 16) = dt;
+ transitionMatrix.at<double>(14, 17) = dt;
+ transitionMatrix.at<double>(9, 15) = 0.5*pow(dt, 2);
+ transitionMatrix.at<double>(10, 16) = 0.5*pow(dt, 2);
+ transitionMatrix.at<double>(11, 17) = 0.5*pow(dt, 2);
+
+
+ /** MEASUREMENT MODEL **/
+
+ // [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
+ // [0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
+ // [0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
+ // [0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0]
+ // [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
+ // [0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0]
+
+ // Those will scale the filtered values, 2 will give you half the raw input
+ measurementMatrix.at<double>(0, 0) = 1; // x
+ measurementMatrix.at<double>(1, 1) = 1; // y
+ measurementMatrix.at<double>(2, 2) = 1; // z
+ measurementMatrix.at<double>(3, 9) = 1; // roll
+ measurementMatrix.at<double>(4, 10) = 1; // pitch
+ measurementMatrix.at<double>(5, 11) = 1; // yaw
+ }
+
+ void KalmanFilterPose::Update(double& aX, double& aY, double& aZ, double& aRoll, double& aPitch, double& aYaw)
+ {
+ // Set measurement to predict
+ iMeasurements.at<double>(0) = aX; // x
+ iMeasurements.at<double>(1) = aY; // y
+ iMeasurements.at<double>(2) = aZ; // z
+ iMeasurements.at<double>(3) = aRoll; // roll
+ iMeasurements.at<double>(4) = aPitch; // pitch
+ iMeasurements.at<double>(5) = aYaw; // yaw
+
+ // First predict, to update the internal statePre variable
+ cv::Mat prediction = predict();
+ // The "correct" phase that is going to use the predicted value and our measurement
+ cv::Mat estimated = correct(iMeasurements);
+ // Estimated translation
+ aX = estimated.at<double>(0);
+ aY = estimated.at<double>(1);
+ aZ = estimated.at<double>(2);
+ // Estimated euler angles
+ aRoll = estimated.at<double>(9);
+ aPitch = estimated.at<double>(10);
+ aYaw = estimated.at<double>(11);
+ }
+
+}
+
diff --git a/tracker-easy/kalman-filter-pose.h b/tracker-easy/kalman-filter-pose.h
new file mode 100644
index 00000000..c454a27c
--- /dev/null
+++ b/tracker-easy/kalman-filter-pose.h
@@ -0,0 +1,32 @@
+/* Copyright (c) 2019, Stephane Lenclud <github@lenclud.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 <opencv2/core.hpp>
+#include <opencv2/video/tracking.hpp>
+
+
+namespace EasyTracker
+{
+
+ ///
+ /// TODO: do not use a constant time difference
+ ///
+ class KalmanFilterPose: public cv::KalmanFilter
+ {
+ public:
+ KalmanFilterPose();
+ void Init(int aStateCount, int aMeasurementCount, int aInputCount, double aDt);
+ void Update(double& aX, double& aY, double& aZ, double& aRoll, double& aPitch, double& aYaw);
+
+
+ cv::Mat iMeasurements;
+ };
+
+}
diff --git a/tracker-easy/lang/de_DE.ts b/tracker-easy/lang/de_DE.ts
new file mode 100644
index 00000000..81e52cb0
--- /dev/null
+++ b/tracker-easy/lang/de_DE.ts
@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>EasyTracker::Metadata</name>
+ <message>
+ <source>Easy Tracker 1.1</source>
+ <translation>Easy Tracker 1.1</translation>
+ </message>
+</context>
+<context>
+ <name>UICPTClientControls</name>
+ <message>
+ <source>Easy Tracker Settings</source>
+ <translation>Easy-Tracker-Einstellungen</translation>
+ </message>
+ <message>
+ <source>Tracker</source>
+ <translation>Tracker</translation>
+ </message>
+ <message>
+ <source>Camera</source>
+ <translation>Kamera</translation>
+ </message>
+ <message>
+ <source>Desired capture framerate</source>
+ <translation>Angestrebte Aufnahmebildrate</translation>
+ </message>
+ <message>
+ <source> Hz</source>
+ <translation> Hz</translation>
+ </message>
+ <message>
+ <source>Camera settings (when available)</source>
+ <translation>Kamera-Einstellungen (falls verfügbar)</translation>
+ </message>
+ <message>
+ <source>Width</source>
+ <translation>Breite</translation>
+ </message>
+ <message>
+ <source>Height</source>
+ <translation>Höhe</translation>
+ </message>
+ <message>
+ <source>FPS</source>
+ <translation>FPS</translation>
+ </message>
+ <message>
+ <source>Open</source>
+ <translation>Öffnen</translation>
+ </message>
+ <message>
+ <source>°</source>
+ <translation>°</translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation>Gerät</translation>
+ </message>
+ <message>
+ <source>Desired capture height</source>
+ <translation>Angestrebte Aufnahmehöhe</translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation> px</translation>
+ </message>
+ <message>
+ <source>Desired capture width</source>
+ <translation>Angestrebte Aufnahmebreite</translation>
+ </message>
+ <message>
+ <source>Diagonal field of view</source>
+ <translation>Diagonales Sichtfeld</translation>
+ </message>
+ <message>
+ <source>Settings</source>
+ <translation>Einstellungen</translation>
+ </message>
+ <message>
+ <source>Debug (full size preview)</source>
+ <translation>Fehlersuche (Vorschau in voller Größe)</translation>
+ </message>
+ <message>
+ <source>Min size</source>
+ <translation>Minimale Größe</translation>
+ </message>
+ <message>
+ <source>Minimum point diameter</source>
+ <translation>Minimaler Punktdurchmesser</translation>
+ </message>
+ <message>
+ <source>Auto center</source>
+ <translation>Automatisch zentrieren</translation>
+ </message>
+ <message>
+ <source>Max size</source>
+ <translation>Maximale Größe</translation>
+ </message>
+ <message>
+ <source>Size in pixels of half the edge defining deadzone squares around tracked points</source>
+ <translation>Größe in Pixeln der halben Kante, die die Totbereichquadrate um die verfolgten Punkte definiert</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use P3P or AP3P for three and four points setup. Use EPNP or ITERATIVE for five points setup. Inconsistent configuration will result in undefined behavior.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Benutze P3P oder AP3P für ein Drei- oder Vierpunkt-Setup. Benutze EPNP oder ITERATIVE für ein Fünfpunkt-Setup. Eine inkonsistente Konfiguration führt zu einem undefinierten Verhalten.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>Perspective-N-Point solver</source>
+ <translation>Perspektivischer N-Punkt-Löser</translation>
+ </message>
+ <message>
+ <source>Make sure you pick a solver supporting the number of marker you are using. For three points detection use either P3P or AP3P.</source>
+ <translation>Stelle sicher, einen Löser zu verwenden, der die Anzahl deiner verwendeten Markierungen unterstützt. Für die Erkennung von drei Punkten nutze entweder P3P oder AP3P.</translation>
+ </message>
+ <message>
+ <source>ITERATIVE</source>
+ <translation>ITERATIVE</translation>
+ </message>
+ <message>
+ <source>EPNP</source>
+ <translation>EPNP</translation>
+ </message>
+ <message>
+ <source>P3P</source>
+ <translation>P3P</translation>
+ </message>
+ <message>
+ <source>DLS</source>
+ <translation>DLS</translation>
+ </message>
+ <message>
+ <source>UPNP</source>
+ <translation>UPNP</translation>
+ </message>
+ <message>
+ <source>AP3P</source>
+ <translation>AP3P</translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation>Totbereich</translation>
+ </message>
+ <message>
+ <source>Maximum point diameter</source>
+ <translation>Maximaler Punktdurchmesser</translation>
+ </message>
+ <message>
+ <source>Auto center timeout</source>
+ <translation>Timeout für automatische Zentrierung</translation>
+ </message>
+ <message>
+ <source>If no valid pose can be determined after that much time the center pose will be used.</source>
+ <translation>Falls innerhalb dieses Zeitraums keine Pose erkannt wird, wird die zentrierte Pose verwendet.</translation>
+ </message>
+ <message>
+ <source> ms</source>
+ <translation> ms</translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation>Modell</translation>
+ </message>
+ <message>
+ <source>Model type:</source>
+ <translation>Modelltyp:</translation>
+ </message>
+ <message>
+ <source>Hat three vertices</source>
+ <translation>Hut drei Eckpunkte</translation>
+ </message>
+ <message>
+ <source>Hat four vertices</source>
+ <translation>Hut vier Eckpunkte</translation>
+ </message>
+ <message>
+ <source>Hat five vertices</source>
+ <translation>Hut fünf Eckpunkte</translation>
+ </message>
+ <message>
+ <source>Clip three vertices</source>
+ <translation>Sticker drei Eckpunkte</translation>
+ </message>
+ <message>
+ <source>Vertices: </source>
+ <translation>Eckpunkte: </translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;X&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;X&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Y&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Y&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Z&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Z&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>Top:</source>
+ <translation>Oben:</translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation> mm</translation>
+ </message>
+ <message>
+ <source>Right:</source>
+ <translation>Rechts:</translation>
+ </message>
+ <message>
+ <source>Left:</source>
+ <translation>Links:</translation>
+ </message>
+ <message>
+ <source>Center:</source>
+ <translation>Mitte:</translation>
+ </message>
+ <message>
+ <source>Top right:</source>
+ <translation>Oben rechts:</translation>
+ </message>
+ <message>
+ <source>Top left:</source>
+ <translation>Oben links:</translation>
+ </message>
+ <message>
+ <source>Clip top:</source>
+ <translation>Sticker-Oberkante:</translation>
+ </message>
+ <message>
+ <source>Clip middle:</source>
+ <translation>Sticker-Mitte:</translation>
+ </message>
+ <message>
+ <source>Clip bottom:</source>
+ <translation>Sticker-Unterkante:</translation>
+ </message>
+ <message>
+ <source>About</source>
+ <translation>Über</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Easy Tracker&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Stéphane Lenclud&lt;/span&gt;&lt;/p&gt;&lt;p&gt;See &lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Easy-Tracker&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#9999aa;&quot;&gt;documentation on GitHub&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Easy Tracker&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;von Stéphane Lenclud&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Siehe &lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Easy-Tracker&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#9999aa;&quot;&gt;Dokumentation auf GitHub&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-easy/lang/nl_NL.ts b/tracker-easy/lang/nl_NL.ts
new file mode 100644
index 00000000..775ec4d9
--- /dev/null
+++ b/tracker-easy/lang/nl_NL.ts
@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>EasyTracker::Metadata</name>
+ <message>
+ <source>Easy Tracker 1.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UICPTClientControls</name>
+ <message>
+ <source>Camera</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>°</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Diagonal field of view</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Width</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FPS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Desired capture height</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Desired capture framerate</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> Hz</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Desired capture width</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Height</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Open</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera settings (when available)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Min size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Maximum point diameter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Minimum point diameter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>About</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Debug (full size preview)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Size in pixels of half the edge defining deadzone squares around tracked points</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Perspective-N-Point solver</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Make sure you pick a solver supporting the number of marker you are using. For three points detection use either P3P or AP3P.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>P3P</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ITERATIVE</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>EPNP</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>DLS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>UPNP</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>AP3P</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;X&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Y&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Z&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Auto center</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use P3P or AP3P for three and four points setup. Use EPNP or ITERATIVE for five points setup. Inconsistent configuration will result in undefined behavior.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Auto center timeout</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>If no valid pose can be determined after that much time the center pose will be used.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> ms</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Easy Tracker Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model type:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hat three vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hat four vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hat five vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip three vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Vertices: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Top:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Right:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Left:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Center:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Top right:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Top left:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip top:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip middle:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip bottom:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Easy Tracker&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Stéphane Lenclud&lt;/span&gt;&lt;/p&gt;&lt;p&gt;See &lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Easy-Tracker&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#9999aa;&quot;&gt;documentation on GitHub&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-easy/lang/ru_RU.ts b/tracker-easy/lang/ru_RU.ts
new file mode 100644
index 00000000..2ae262a5
--- /dev/null
+++ b/tracker-easy/lang/ru_RU.ts
@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>EasyTracker::Metadata</name>
+ <message>
+ <source>Easy Tracker 1.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UICPTClientControls</name>
+ <message>
+ <source>Camera</source>
+ <translation>Камера</translation>
+ </message>
+ <message>
+ <source>°</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Diagonal field of view</source>
+ <translation>Угол обзора камеры</translation>
+ </message>
+ <message>
+ <source>Width</source>
+ <translation>Ширина</translation>
+ </message>
+ <message>
+ <source>FPS</source>
+ <translation>FPS (Кадров в Ñекунду)</translation>
+ </message>
+ <message>
+ <source>Desired capture height</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Desired capture framerate</source>
+ <translation>Ð–ÐµÐ»Ð°ÐµÐ¼Ð°Ñ Ñ‡Ð°Ñтота кадров</translation>
+ </message>
+ <message>
+ <source> Hz</source>
+ <translation> Гц</translation>
+ </message>
+ <message>
+ <source>Desired capture width</source>
+ <translation>Ð–ÐµÐ»Ð°ÐµÐ¼Ð°Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ð° захвата</translation>
+ </message>
+ <message>
+ <source>Height</source>
+ <translation>Ð’Ñ‹Ñота</translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation>УÑтройÑтво</translation>
+ </message>
+ <message>
+ <source>Open</source>
+ <translation>Открыть</translation>
+ </message>
+ <message>
+ <source>Camera settings (when available)</source>
+ <translation>Параметры камеры (еÑли доÑтупно)</translation>
+ </message>
+ <message>
+ <source>Max size</source>
+ <translation>МакÑ.размер</translation>
+ </message>
+ <message>
+ <source>Min size</source>
+ <translation>Мин.размер</translation>
+ </message>
+ <message>
+ <source>Maximum point diameter</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Minimum point diameter</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation>Модель</translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation> мм</translation>
+ </message>
+ <message>
+ <source>About</source>
+ <translation>О программе</translation>
+ </message>
+ <message>
+ <source>Debug (full size preview)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Size in pixels of half the edge defining deadzone squares around tracked points</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Perspective-N-Point solver</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Make sure you pick a solver supporting the number of marker you are using. For three points detection use either P3P or AP3P.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>P3P</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ITERATIVE</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>EPNP</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>DLS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>UPNP</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>AP3P</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;X&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Y&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Z&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Auto center</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use P3P or AP3P for three and four points setup. Use EPNP or ITERATIVE for five points setup. Inconsistent configuration will result in undefined behavior.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Auto center timeout</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>If no valid pose can be determined after that much time the center pose will be used.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> ms</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Easy Tracker Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model type:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hat three vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hat four vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hat five vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip three vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Vertices: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Top:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Right:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Left:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Center:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Top right:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Top left:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip top:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip middle:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip bottom:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Easy Tracker&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Stéphane Lenclud&lt;/span&gt;&lt;/p&gt;&lt;p&gt;See &lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Easy-Tracker&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#9999aa;&quot;&gt;documentation on GitHub&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-easy/lang/stub.ts b/tracker-easy/lang/stub.ts
new file mode 100644
index 00000000..b10f5885
--- /dev/null
+++ b/tracker-easy/lang/stub.ts
@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>EasyTracker::Metadata</name>
+ <message>
+ <source>Easy Tracker 1.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UICPTClientControls</name>
+ <message>
+ <source>Camera</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>°</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Diagonal field of view</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Width</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FPS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Desired capture height</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Desired capture framerate</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> Hz</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Desired capture width</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Height</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Open</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera settings (when available)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Min size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Maximum point diameter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Minimum point diameter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>About</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Debug (full size preview)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Size in pixels of half the edge defining deadzone squares around tracked points</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Perspective-N-Point solver</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Make sure you pick a solver supporting the number of marker you are using. For three points detection use either P3P or AP3P.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>P3P</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ITERATIVE</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>EPNP</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>DLS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>UPNP</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>AP3P</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;X&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Y&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Z&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Auto center</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use P3P or AP3P for three and four points setup. Use EPNP or ITERATIVE for five points setup. Inconsistent configuration will result in undefined behavior.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Auto center timeout</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>If no valid pose can be determined after that much time the center pose will be used.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> ms</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Easy Tracker Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model type:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hat three vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hat four vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hat five vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip three vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Vertices: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Top:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Right:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Left:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Center:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Top right:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Top left:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip top:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip middle:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip bottom:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Easy Tracker&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Stéphane Lenclud&lt;/span&gt;&lt;/p&gt;&lt;p&gt;See &lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Easy-Tracker&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#9999aa;&quot;&gt;documentation on GitHub&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-easy/lang/zh_CN.ts b/tracker-easy/lang/zh_CN.ts
new file mode 100644
index 00000000..71fc6368
--- /dev/null
+++ b/tracker-easy/lang/zh_CN.ts
@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>EasyTracker::Metadata</name>
+ <message>
+ <source>Easy Tracker 1.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UICPTClientControls</name>
+ <message>
+ <source>Camera</source>
+ <translation>æ‘„åƒå¤´</translation>
+ </message>
+ <message>
+ <source>°</source>
+ <translation>度</translation>
+ </message>
+ <message>
+ <source>Diagonal field of view</source>
+ <translation>对角线</translation>
+ </message>
+ <message>
+ <source>Width</source>
+ <translation>宽度</translation>
+ </message>
+ <message>
+ <source>FPS</source>
+ <translation>帧数</translation>
+ </message>
+ <message>
+ <source>Desired capture height</source>
+ <translation>期望高度</translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation> åƒç´ ç‚¹</translation>
+ </message>
+ <message>
+ <source>Desired capture framerate</source>
+ <translation>期望帧数</translation>
+ </message>
+ <message>
+ <source> Hz</source>
+ <translation> 赫兹</translation>
+ </message>
+ <message>
+ <source>Desired capture width</source>
+ <translation>期望宽度</translation>
+ </message>
+ <message>
+ <source>Height</source>
+ <translation>高度</translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation>设备åç§°</translation>
+ </message>
+ <message>
+ <source>Open</source>
+ <translation>打开</translation>
+ </message>
+ <message>
+ <source>Camera settings (when available)</source>
+ <translation>æ‘„åƒå¤´è®¾ç½® (连接时)</translation>
+ </message>
+ <message>
+ <source>Max size</source>
+ <translation>最大</translation>
+ </message>
+ <message>
+ <source>Min size</source>
+ <translation>最å°</translation>
+ </message>
+ <message>
+ <source>Maximum point diameter</source>
+ <translation>最大点直径</translation>
+ </message>
+ <message>
+ <source>Minimum point diameter</source>
+ <translation>最å°ç‚¹ç›´å¾„</translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation>点模å¼</translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation> 毫米</translation>
+ </message>
+ <message>
+ <source>About</source>
+ <translation>关于</translation>
+ </message>
+ <message>
+ <source>Debug (full size preview)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Size in pixels of half the edge defining deadzone squares around tracked points</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Perspective-N-Point solver</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Make sure you pick a solver supporting the number of marker you are using. For three points detection use either P3P or AP3P.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>P3P</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ITERATIVE</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>EPNP</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>DLS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>UPNP</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>AP3P</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;X&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Y&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Z&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Auto center</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use P3P or AP3P for three and four points setup. Use EPNP or ITERATIVE for five points setup. Inconsistent configuration will result in undefined behavior.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Auto center timeout</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>If no valid pose can be determined after that much time the center pose will be used.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> ms</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Easy Tracker Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model type:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hat three vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hat four vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hat five vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip three vertices</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Vertices: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Top:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Right:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Left:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Center:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Top right:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Top left:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip top:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip middle:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip bottom:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Easy Tracker&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Stéphane Lenclud&lt;/span&gt;&lt;/p&gt;&lt;p&gt;See &lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Easy-Tracker&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#9999aa;&quot;&gt;documentation on GitHub&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-easy/module.cpp b/tracker-easy/module.cpp
new file mode 100644
index 00000000..8fcfeae1
--- /dev/null
+++ b/tracker-easy/module.cpp
@@ -0,0 +1,15 @@
+#include "tracker-easy.h"
+#include "tracker-easy-dialog.h"
+#include "module.hpp"
+
+#include <memory>
+
+namespace EasyTracker
+{
+ QString Metadata::name() { return tr("Easy Tracker 1.1"); }
+ QIcon Metadata::icon() { return QIcon(":/Resources/easy-tracker-logo.png"); }
+
+}
+
+
+OPENTRACK_DECLARE_TRACKER(EasyTracker::Tracker, EasyTracker::Dialog, EasyTracker::Metadata)
diff --git a/tracker-easy/module.hpp b/tracker-easy/module.hpp
new file mode 100644
index 00000000..8a4e3920
--- /dev/null
+++ b/tracker-easy/module.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "api/plugin-api.hpp"
+#include <QIcon>
+#include <QString>
+
+#include "compat/linkage-macros.hpp"
+
+namespace EasyTracker
+{
+
+ class OTR_GENERIC_EXPORT Metadata : public ::Metadata
+ {
+ Q_OBJECT
+
+ QString name() override;
+ QIcon icon() override;
+ };
+
+} // ns pt_module
diff --git a/tracker-easy/ocv-check.cxx b/tracker-easy/ocv-check.cxx
new file mode 100644
index 00000000..912bd3cf
--- /dev/null
+++ b/tracker-easy/ocv-check.cxx
@@ -0,0 +1,18 @@
+#include <opencv2/calib3d.hpp>
+void check_solvep3p()
+{
+ cv::Mat x;
+ cv::solveP3P(x, x, x, x, x, x, 0);
+}
+
+#include <opencv2/video/tracking.hpp>
+void check_kf()
+{
+ [[maybe_unused]] cv::KalmanFilter kf;
+}
+
+#include <opencv2/highgui.hpp>
+void check_highgui()
+{
+ cv::imshow("foo", cv::noArray());
+}
diff --git a/tracker-easy/point-extractor.cpp b/tracker-easy/point-extractor.cpp
new file mode 100644
index 00000000..34d93b52
--- /dev/null
+++ b/tracker-easy/point-extractor.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2019 Stephane Lenclud
+ *
+ * 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 "point-extractor.h"
+#include "preview.h"
+#include "tracker-easy.h"
+
+#include "cv/numeric.hpp"
+#include "compat/math.hpp"
+
+#include <cmath>
+#include <algorithm>
+#include <cinttypes>
+#include <memory>
+#include <opencv2/highgui/highgui.hpp>
+// Needed for mean shift
+#include <opencv2/video/tracking.hpp>
+
+#include <QDebug>
+
+
+using namespace numeric_types;
+
+namespace EasyTracker
+{
+
+ PointExtractor::PointExtractor() : iSettings(KModuleName)
+ {
+ UpdateSettings();
+ }
+
+ ///
+ void PointExtractor::UpdateSettings()
+ {
+ iMinPointSize = iSettings.iMinBlobSize;
+ iMaxPointSize = iSettings.iMaxBlobSize;
+ }
+
+ ///
+ void PointExtractor::ExtractPoints(const cv::Mat& aFrame, cv::Mat* aPreview, int aNeededPointCount, std::vector<cv::Point>& aPoints)
+ {
+ //TODO: Assert if channel size is neither one nor two
+ // Make sure our frame channel is 8 bit
+ size_t channelSize = aFrame.elemSize1();
+ if (channelSize == 2)
+ {
+ // We have a 16 bits single channel. Typically coming from Kinect V2 IR sensor
+ // Resample to 8-bits
+ double min = std::numeric_limits<uint16_t>::min();
+ double max = std::numeric_limits<uint16_t>::max();
+ //cv::minMaxLoc(raw, &min, &max); // Should we use 16bit min and max instead?
+ // For scalling to have more precission in the range we are interrested in
+ min = max - 255;
+ // See: https://stackoverflow.com/questions/14539498/change-type-of-mat-object-from-cv-32f-to-cv-8u/14539652
+ aFrame.convertTo(iFrameChannelSizeOne, CV_8U, 255.0 / (max - min), -255.0*min / (max - min));
+ }
+ else
+ {
+ iFrameChannelSizeOne = aFrame;
+ }
+
+
+ // Make sure our frame has a single channel
+ // Make an extra copy if needed
+ const int channelCount = iFrameChannelSizeOne.channels();
+ if (channelCount == 3)
+ {
+ // Convert to grayscale
+ // TODO: What's our input format, BRG or RGB?
+ // That won't make our point extraction work but at least it won't crash
+ cv::cvtColor(iFrameChannelSizeOne, iFrameGray, cv::COLOR_BGR2GRAY);
+ // TODO: Instead convert to HSV and use a key color together with cv::inRange to sport the color we want.
+ // Key color should be defined in settings.
+ }
+ else if (channelCount == 1)
+ {
+ // No further convertion needed
+ iFrameGray = iFrameChannelSizeOne;
+ }
+ else
+ {
+ eval_once(qDebug() << "tracker/easy: camera frame depth not supported" << aFrame.channels());
+ return;
+ }
+
+//#define DEBUG
+#ifdef DEBUG
+ cv::imshow("iFrameGray", iFrameGray);
+ cv::erode(iFrameGray, iFrameGray, cv::Mat(), cv::Point(-1, -1), 1);
+ cv::imshow("Eroded", iFrameGray);
+ cv::dilate(iFrameGray, iFrameGray, cv::Mat(), cv::Point(-1, -1), 2);
+ cv::imshow("Dilated", iFrameGray);
+#endif
+
+
+ // Contours detection
+ iContours.clear();
+ cv::findContours(iFrameGray, iContours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
+
+ // Workout which countours are valid points
+ for (size_t i = 0; i < iContours.size(); i++)
+ {
+ if (aPreview)
+ {
+ cv::drawContours(*aPreview, iContours, (int)i, CV_RGB(255, 0, 0), 2);
+ }
+
+
+ cv::Rect bBox;
+ bBox = cv::boundingRect(iContours[i]);
+
+ // Make sure bounding box matches our criteria
+ if (bBox.width >= iMinPointSize
+ && bBox.height >= iMinPointSize
+ && bBox.width <= iMaxPointSize
+ && bBox.height <= iMaxPointSize)
+ {
+ // Do a mean shift or cam shift, it's not bringing much though
+ //cv::CamShift(iFrameGray, bBox, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 10, 1));
+ //
+ cv::Point center;
+ center.x = bBox.x + bBox.width / 2;
+ center.y = bBox.y + bBox.height / 2;
+ aPoints.push_back(center);
+
+ if (aPreview)
+ {
+ cv::rectangle(*aPreview, bBox, CV_RGB(0, 255, 0), 2);
+ }
+ }
+ }
+
+ // Keep only the three points which are highest, i.e. with lowest Y coordinates
+ // That's most usefull to discard noise from features below your cap/head.
+ // Typically noise comming from zippers and metal parts on your clothing.
+ // With a cap tracker it also successfully discards noise from glasses.
+ // However it may not work as good with a clip user wearing glasses.
+ while (aPoints.size() > aNeededPointCount) // Until we have no more than three points
+ {
+ int maxY = 0;
+ size_t index = std::numeric_limits<size_t>::max();
+
+ // Search for the point with highest Y coordinate
+ for (size_t i = 0; i < aPoints.size(); i++)
+ {
+ if (aPoints[i].y > maxY)
+ {
+ maxY = aPoints[i].y;
+ index = i;
+ }
+ }
+
+ if (index < aPoints.size()) // Defensive
+ {
+ // Discard it
+ aPoints.erase(aPoints.begin() + index);
+ }
+
+ }
+ }
+
+}
+
diff --git a/tracker-easy/point-extractor.h b/tracker-easy/point-extractor.h
new file mode 100644
index 00000000..f275769d
--- /dev/null
+++ b/tracker-easy/point-extractor.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019 Stephane Lenclud
+ *
+ * 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 "settings.h"
+#include <vector>
+
+#include <opencv2/core.hpp>
+#include <opencv2/imgproc.hpp>
+
+
+namespace EasyTracker
+{
+ class PointExtractor
+ {
+ public:
+ PointExtractor();
+ // extracts points from frame and draws some processing info into frame, if draw_output is set
+ // dt: time since last call in seconds
+ void ExtractPoints(const cv::Mat& aFrame, cv::Mat* aPreview, int aNeededPointCount, std::vector<cv::Point>& aPoints);
+
+ void UpdateSettings();
+
+ // Settings
+ Settings iSettings;
+ // Our frame with a channel size of 8 bits
+ cv::Mat iFrameChannelSizeOne;
+ // Our frame with a single 8 bits channel
+ cv::Mat iFrameGray;
+ //
+ std::vector<std::vector<cv::Point> > iContours;
+
+ // Take a copy of settings to avoid dead lock
+ int iMinPointSize;
+ int iMaxPointSize;
+
+ };
+
+}
+
diff --git a/tracker-easy/preview.cpp b/tracker-easy/preview.cpp
new file mode 100644
index 00000000..97c8aeaf
--- /dev/null
+++ b/tracker-easy/preview.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2019 Stephane Lenclud
+ *
+ * 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 "preview.h"
+
+#include "compat/math.hpp"
+#include "compat/macros.h"
+
+#include <opencv2/imgproc.hpp>
+#include <QDebug>
+
+
+namespace EasyTracker
+{
+
+
+ Preview& Preview::operator=(const cv::Mat& aFrame)
+ {
+ //TODO: Assert if channel size is neither one nor two
+
+ // Make sure our frame channel is 8 bit
+ size_t channelSize = aFrame.elemSize1();
+ if (channelSize == 2)
+ {
+ // First resample to 8-bits
+ double min = std::numeric_limits<uint16_t>::min();
+ double max = std::numeric_limits<uint16_t>::max();
+ //cv::minMaxLoc(raw, &min, &max); // Should we use 16bit min and max instead?
+ // For scalling to have more precission in the range we are interrested in
+ //min = max - 255;
+ // See: https://stackoverflow.com/questions/14539498/change-type-of-mat-object-from-cv-32f-to-cv-8u/14539652
+ aFrame.convertTo(iFrameChannelSizeOne, CV_8U, 255.0 / (max - min), -255.0*min / (max - min));
+ }
+ else
+ {
+ iFrameChannelSizeOne = aFrame;
+ }
+
+ // Make sure our frame is RGB
+ // Make an extra copy if needed
+ int channelCount = iFrameChannelSizeOne.channels();
+ if (channelCount == 1)
+ {
+ // Convert to RGB
+ cv::cvtColor(iFrameChannelSizeOne, iFrameRgb, cv::COLOR_GRAY2BGR);
+ }
+ else if (channelCount == 3)
+ {
+ iFrameRgb = iFrameChannelSizeOne;
+ }
+ else
+ {
+ eval_once(qDebug() << "tracker/easy: camera frame depth not supported" << aFrame.channels());
+ return *this;
+ }
+
+
+ return *this;
+ }
+
+ Preview::Preview(int w, int h)
+ {
+ ensure_size(iFrameWidget, w, h, CV_8UC4);
+ ensure_size(iFrameResized, w, h, CV_8UC3);
+
+ iFrameResized.setTo(cv::Scalar(0, 0, 0));
+ }
+
+ QImage Preview::get_bitmap()
+ {
+ size_t stride = iFrameWidget.step.p[0];
+
+ if (stride < 64 || stride < iFrameWidget.cols * 4)
+ {
+ eval_once(qDebug() << "bad stride" << stride
+ << "for bitmap size" << iFrameResized.cols << iFrameResized.rows);
+ return QImage();
+ }
+
+ // Resize if needed
+ const bool need_resize = iFrameRgb.cols != iFrameWidget.cols || iFrameRgb.rows != iFrameWidget.rows;
+ if (need_resize)
+ {
+ cv::resize(iFrameRgb, iFrameResized, cv::Size(iFrameWidget.cols, iFrameWidget.rows), 0, 0, cv::INTER_NEAREST);
+ }
+ else
+ {
+ iFrameResized = iFrameRgb;
+ }
+
+ cv::cvtColor(iFrameResized, iFrameWidget, cv::COLOR_BGR2BGRA);
+
+ return QImage((const unsigned char*)iFrameWidget.data,
+ iFrameWidget.cols, iFrameWidget.rows,
+ (int)stride,
+ QImage::Format_ARGB32);
+ }
+
+ void Preview::DrawInfo(const std::string& aString)
+ {
+ cv::putText(iFrameRgb, aString, cv::Point(4,iFrameRgb.size().height-4), cv::FONT_HERSHEY_PLAIN, 2, cv::Scalar(0, 255, 0),2);
+ }
+
+ void Preview::DrawCross(const cv::Point& aPoint, const cv::Scalar& aColor)
+ {
+ constexpr int len = 9;
+ cv::line(iFrameRgb,
+ cv::Point(aPoint.x - len, aPoint.y),
+ cv::Point(aPoint.x + len, aPoint.y),
+ aColor, 2);
+ cv::line(iFrameRgb,
+ cv::Point(aPoint.x, aPoint.y - len),
+ cv::Point(aPoint.x, aPoint.y + len),
+ aColor, 2);
+ }
+
+ void Preview::ensure_size(cv::Mat& frame, int w, int h, int type)
+ {
+ if (frame.cols != w || frame.rows != h || frame.type() != type)
+ frame = cv::Mat(h, w, type);
+ }
+
+}
diff --git a/tracker-easy/preview.h b/tracker-easy/preview.h
new file mode 100644
index 00000000..d5cc8c30
--- /dev/null
+++ b/tracker-easy/preview.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019 Stephane Lenclud
+ *
+ * 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 <opencv2/core.hpp>
+#include <QImage>
+
+namespace EasyTracker
+{
+
+ struct Preview
+ {
+ Preview(int w, int h);
+
+ Preview& operator=(const cv::Mat& frame);
+ QImage get_bitmap();
+ void DrawCross(const cv::Point& aPoint, const cv::Scalar& aColor);
+ void DrawInfo(const std::string& aString);
+
+ operator cv::Mat&() { return iFrameResized; }
+ operator cv::Mat const&() const { return iFrameResized; }
+
+ private:
+ static void ensure_size(cv::Mat& frame, int w, int h, int type);
+
+ public:
+ // Frame with an 8 bits channel size
+ cv::Mat iFrameChannelSizeOne;
+ // Frame with three channels for RGB
+ cv::Mat iFrameRgb;
+ // Frame rezised to widget dimension
+ cv::Mat iFrameResized;
+ // Frame ready to be packed in a QImage for use in our widget
+ cv::Mat iFrameWidget;
+
+ };
+
+}
+
diff --git a/tracker-easy/settings.h b/tracker-easy/settings.h
new file mode 100644
index 00000000..4141ebe0
--- /dev/null
+++ b/tracker-easy/settings.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "options/options.hpp"
+#include <opencv2/calib3d.hpp>
+
+#include <QString>
+
+
+namespace EasyTracker {
+
+ using namespace options;
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
+ struct Settings final : options::opts
+ {
+ using slider_value = options::slider_value;
+
+ value<QString> camera_name{ b, "camera-name", "" };
+ value<int> cam_res_x{ b, "camera-res-width", 640 },
+ cam_res_y{ b, "camera-res-height", 480 },
+ cam_fps{ b, "camera-fps", 30 };
+ value<int> iMinBlobSize{ b, "iMinBlobSize", 4 }, iMaxBlobSize{ b, "iMaxBlobSize", 15 };
+ value<int> DeadzoneRectHalfEdgeSize { b, "deadzone-rect-half-edge-size", 1 };
+
+ // Type of custom model
+ value<bool> iCustomModelThree{ b, "iCustomModelThree", true };
+ value<bool> iCustomModelFour{ b, "iCustomModelFour", false };
+ value<bool> iCustomModelFive{ b, "iCustomModelFive", false };
+ value<bool> iClipModelThree{ b, "iClipModelThree", false };
+
+ // Custom model vertices
+ value<int> iVertexTopX{ b, "iVertexTopX", 0 }, iVertexTopY{ b, "iVertexTopY", 0 }, iVertexTopZ{ b, "iVertexTopZ", 0 };
+ value<int> iVertexRightX{ b, "iVertexRightX", 0 }, iVertexRightY{ b, "iVertexRightY", 0 }, iVertexRightZ{ b, "iVertexRightZ", 0 };
+ value<int> iVertexLeftX{ b, "iVertexLeftX", 0 }, iVertexLeftY{ b, "iVertexLeftY", 0 }, iVertexLeftZ{ b, "iVertexLeftZ", 0 };
+ value<int> iVertexCenterX{ b, "iVertexCenterX", 0 }, iVertexCenterY{ b, "iVertexCenterY", 0 }, iVertexCenterZ{ b, "iVertexCenterZ", 0 };
+ value<int> iVertexTopRightX{ b, "iVertexTopRightX", 0 }, iVertexTopRightY{ b, "iVertexTopRightY", 0 }, iVertexTopRightZ{ b, "iVertexTopRightZ", 0 };
+ value<int> iVertexTopLeftX{ b, "iVertexTopLeftX", 0 }, iVertexTopLeftY{ b, "iVertexTopLeftY", 0 }, iVertexTopLeftZ{ b, "iVertexTopLeftZ", 0 };
+ // Clip model vertices
+ value<int> iVertexClipTopX{ b, "iVertexClipTopX", 0 }, iVertexClipTopY{ b, "iVertexClipTopY", 0 }, iVertexClipTopZ{ b, "iVertexClipTopZ", 0 };
+ value<int> iVertexClipMiddleX{ b, "iVertexClipMiddleX", 0 }, iVertexClipMiddleY{ b, "iVertexClipMiddleY", 0 }, iVertexClipMiddleZ{ b, "iVertexClipMiddleZ", 0 };
+ value<int> iVertexClipBottomX{ b, "iVertexClipBottomX", 0 }, iVertexClipBottomY{ b, "iVertexClipBottomY", 0 }, iVertexClipBottomZ{ b, "iVertexClipBottomZ", 0 };
+
+
+ value<int> fov{ b, "camera-fov", 56 };
+
+ value<bool> debug{ b, "debug", false };
+ value<bool> iAutoCenter{ b, "iAutoCenter", true };
+ value<int> iAutoCenterTimeout{ b, "iAutoCenterTimeout", 1000 };
+
+
+ value<int> PnpSolver{ b, "pnp-solver", cv::SOLVEPNP_P3P };
+
+
+ explicit Settings(const QString& name) : opts(name) {}
+ };
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
+} //
diff --git a/tracker-easy/tracker-easy-dialog.cpp b/tracker-easy/tracker-easy-dialog.cpp
new file mode 100644
index 00000000..b0870b50
--- /dev/null
+++ b/tracker-easy/tracker-easy-dialog.cpp
@@ -0,0 +1,217 @@
+/* Copyright (c) 2012 Patrick Ruoff
+ * Copyright (c) 2014-2015 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * 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 "tracker-easy-dialog.h"
+#include "compat/math.hpp"
+#include "video/camera.hpp"
+
+#include <opencv2/core.hpp>
+#include <opencv2/calib3d.hpp>
+
+#include <QString>
+#include <QtGlobal>
+#include <QDebug>
+
+using namespace options;
+
+static void init_resources() { Q_INIT_RESOURCE(tracker_easy); }
+
+namespace EasyTracker
+{
+
+ Dialog::Dialog() :
+ s(KModuleName),
+ tracker(nullptr)
+ {
+ init_resources();
+
+ ui.setupUi(this);
+
+ for (const QString& str : video::camera_names())
+ ui.camdevice_combo->addItem(str);
+
+ tie_setting(s.camera_name, ui.camdevice_combo);
+ tie_setting(s.cam_res_x, ui.res_x_spin);
+ tie_setting(s.cam_res_y, ui.res_y_spin);
+ tie_setting(s.cam_fps, ui.fps_spin);
+
+ tie_setting(s.iMinBlobSize, ui.mindiam_spin);
+ tie_setting(s.iMaxBlobSize, ui.maxdiam_spin);
+ tie_setting(s.DeadzoneRectHalfEdgeSize, ui.spinDeadzone);
+
+ tie_setting(s.iVertexTopX, ui.iSpinVertexTopX);
+ tie_setting(s.iVertexTopY, ui.iSpinVertexTopY);
+ tie_setting(s.iVertexTopZ, ui.iSpinVertexTopZ);
+
+ tie_setting(s.iVertexRightX, ui.iSpinVertexRightX);
+ tie_setting(s.iVertexRightY, ui.iSpinVertexRightY);
+ tie_setting(s.iVertexRightZ, ui.iSpinVertexRightZ);
+
+ tie_setting(s.iVertexLeftX, ui.iSpinVertexLeftX);
+ tie_setting(s.iVertexLeftY, ui.iSpinVertexLeftY);
+ tie_setting(s.iVertexLeftZ, ui.iSpinVertexLeftZ);
+
+ tie_setting(s.iVertexCenterX, ui.iSpinVertexCenterX);
+ tie_setting(s.iVertexCenterY, ui.iSpinVertexCenterY);
+ tie_setting(s.iVertexCenterZ, ui.iSpinVertexCenterZ);
+
+ tie_setting(s.iVertexTopRightX, ui.iSpinVertexTopRightX);
+ tie_setting(s.iVertexTopRightY, ui.iSpinVertexTopRightY);
+ tie_setting(s.iVertexTopRightZ, ui.iSpinVertexTopRightZ);
+
+ tie_setting(s.iVertexTopLeftX, ui.iSpinVertexTopLeftX);
+ tie_setting(s.iVertexTopLeftY, ui.iSpinVertexTopLeftY);
+ tie_setting(s.iVertexTopLeftZ, ui.iSpinVertexTopLeftZ);
+
+ // Clip model
+ tie_setting(s.iVertexClipTopX, ui.iSpinVertexClipTopX);
+ tie_setting(s.iVertexClipTopY, ui.iSpinVertexClipTopY);
+ tie_setting(s.iVertexClipTopZ, ui.iSpinVertexClipTopZ);
+
+ tie_setting(s.iVertexClipMiddleX, ui.iSpinVertexClipMiddleX);
+ tie_setting(s.iVertexClipMiddleY, ui.iSpinVertexClipMiddleY);
+ tie_setting(s.iVertexClipMiddleZ, ui.iSpinVertexClipMiddleZ);
+
+ tie_setting(s.iVertexClipBottomX, ui.iSpinVertexClipBottomX);
+ tie_setting(s.iVertexClipBottomY, ui.iSpinVertexClipBottomY);
+ tie_setting(s.iVertexClipBottomZ, ui.iSpinVertexClipBottomZ);
+
+ tie_setting(s.fov, ui.fov);
+
+ tie_setting(s.debug, ui.debug);
+
+ tie_setting(s.iAutoCenter, ui.iCheckBoxAutoCenter);
+ tie_setting(s.iAutoCenterTimeout, ui.iSpinBoxAutoCenterTimeout);
+
+
+ connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
+ connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
+
+ connect(ui.camdevice_combo, &QComboBox::currentTextChanged, this, &Dialog::set_camera_settings_available);
+ set_camera_settings_available(ui.camdevice_combo->currentText());
+ connect(ui.camera_settings, &QPushButton::clicked, this, &Dialog::show_camera_settings);
+
+ // Radio Button
+ connect(ui.iRadioButtonCustomModelThree, &QRadioButton::clicked, this, &Dialog::UpdateCustomModelControls);
+ connect(ui.iRadioButtonCustomModelFour, &QRadioButton::clicked, this, &Dialog::UpdateCustomModelControls);
+ connect(ui.iRadioButtonCustomModelFive, &QRadioButton::clicked, this, &Dialog::UpdateCustomModelControls);
+ connect(ui.iRadioButtonClipModelThree, &QRadioButton::clicked, this, &Dialog::UpdateCustomModelControls);
+
+ tie_setting(s.iCustomModelThree, ui.iRadioButtonCustomModelThree);
+ tie_setting(s.iCustomModelFour, ui.iRadioButtonCustomModelFour);
+ tie_setting(s.iCustomModelFive, ui.iRadioButtonCustomModelFive);
+ tie_setting(s.iClipModelThree, ui.iRadioButtonClipModelThree);
+
+
+ for (unsigned k = 0; k < cv::SOLVEPNP_MAX_COUNT; k++)
+ {
+ ui.comboBoxSolvers->setItemData(k, k);
+ }
+
+
+ tie_setting(s.PnpSolver, ui.comboBoxSolvers);
+
+ UpdateCustomModelControls();
+ }
+
+ void Dialog::UpdateCustomModelControls()
+ {
+ if (ui.iRadioButtonCustomModelThree->isChecked())
+ {
+ ui.iGroupBoxCenter->hide();
+ ui.iGroupBoxTopRight->hide();
+ ui.iGroupBoxTopLeft->hide();
+ ui.iGroupBoxTop->show();
+ ui.iGroupBoxRight->show();
+ ui.iGroupBoxLeft->show();
+ ui.iGroupBoxClipTop->hide();
+ ui.iGroupBoxClipMiddle->hide();
+ ui.iGroupBoxClipBottom->hide();
+ }
+ else if (ui.iRadioButtonCustomModelFour->isChecked())
+ {
+ ui.iGroupBoxCenter->show();
+ ui.iGroupBoxTopRight->hide();
+ ui.iGroupBoxTopLeft->hide();
+ ui.iGroupBoxTop->show();
+ ui.iGroupBoxRight->show();
+ ui.iGroupBoxLeft->show();
+ ui.iGroupBoxClipTop->hide();
+ ui.iGroupBoxClipMiddle->hide();
+ ui.iGroupBoxClipBottom->hide();
+ }
+ else if (ui.iRadioButtonCustomModelFive->isChecked())
+ {
+ ui.iGroupBoxCenter->hide();
+ ui.iGroupBoxTopRight->show();
+ ui.iGroupBoxTopLeft->show();
+ ui.iGroupBoxTop->show();
+ ui.iGroupBoxRight->show();
+ ui.iGroupBoxLeft->show();
+ ui.iGroupBoxClipTop->hide();
+ ui.iGroupBoxClipMiddle->hide();
+ ui.iGroupBoxClipBottom->hide();
+ }
+ else if (ui.iRadioButtonClipModelThree->isChecked())
+ {
+ ui.iGroupBoxTop->hide();
+ ui.iGroupBoxRight->hide();
+ ui.iGroupBoxLeft->hide();
+ ui.iGroupBoxCenter->hide();
+ ui.iGroupBoxTopRight->hide();
+ ui.iGroupBoxTopLeft->hide();
+ ui.iGroupBoxClipTop->show();
+ ui.iGroupBoxClipMiddle->show();
+ ui.iGroupBoxClipBottom->show();
+ }
+ }
+
+
+ void Dialog::set_camera_settings_available(const QString& /* camera_name */)
+ {
+ ui.camera_settings->setEnabled(true);
+ }
+
+ void Dialog::show_camera_settings()
+ {
+ if (tracker)
+ {
+ QMutexLocker l(&tracker->camera_mtx);
+ (void)tracker->camera->show_dialog();
+ }
+ else
+ (void)video::show_dialog(s.camera_name);
+ }
+
+
+ void Dialog::save()
+ {
+ s.b->save();
+ }
+
+ void Dialog::doOK()
+ {
+ save();
+ close();
+ }
+
+ void Dialog::doCancel()
+ {
+ close();
+ }
+
+ void Dialog::register_tracker(ITracker *t)
+ {
+ tracker = static_cast<Tracker*>(t);
+ }
+
+ void Dialog::unregister_tracker()
+ {
+ tracker = nullptr;
+ }
+}
diff --git a/tracker-easy/tracker-easy-dialog.h b/tracker-easy/tracker-easy-dialog.h
new file mode 100644
index 00000000..7eb1ffb1
--- /dev/null
+++ b/tracker-easy/tracker-easy-dialog.h
@@ -0,0 +1,48 @@
+/* Copyright (c) 2012 Patrick Ruoff
+ *
+ * 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 "tracker-easy.h"
+#include "settings.h"
+#include "ui_tracker-easy-settings.h"
+#include "cv/translation-calibrator.hpp"
+#include "video/video-widget.hpp"
+
+#include <QTimer>
+#include <QMutex>
+
+namespace EasyTracker
+{
+ class Dialog : public ITrackerDialog
+ {
+ Q_OBJECT
+ public:
+ Dialog();
+ void register_tracker(ITracker *tracker) override;
+ void unregister_tracker() override;
+ void save();
+ private:
+ void UpdateCustomModelControls();
+
+ public slots:
+ void doOK();
+ void doCancel();
+
+ void set_camera_settings_available(const QString& camera_name);
+ void show_camera_settings();
+ signals:
+ void poll_tracker_info();
+ protected:
+
+ Settings s;
+ Tracker* tracker;
+
+ Ui::UICPTClientControls ui;
+ };
+
+}
diff --git a/tracker-easy/tracker-easy-settings.ui b/tracker-easy/tracker-easy-settings.ui
new file mode 100644
index 00000000..d6cc86b6
--- /dev/null
+++ b/tracker-easy/tracker-easy-settings.ui
@@ -0,0 +1,1118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UICPTClientControls</class>
+ <widget class="QWidget" name="UICPTClientControls">
+ <property name="windowModality">
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>328</width>
+ <height>923</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Easy Tracker Settings</string>
+ </property>
+ <property name="windowIcon">
+ <iconset>
+ <normaloff>:/Resources/tracker-easy-logo.png</normaloff>:/Resources/tracker-easy-logo.png</iconset>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item alignment="Qt::AlignTop">
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="locale">
+ <locale language="English" country="UnitedStates"/>
+ </property>
+ <property name="currentIndex">
+ <number>2</number>
+ </property>
+ <widget class="QWidget" name="tabTracker">
+ <attribute name="title">
+ <string>Tracker</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="camera_settings_groupbox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Camera</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="3" column="1">
+ <widget class="QSpinBox" name="fps_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Desired capture framerate</string>
+ </property>
+ <property name="suffix">
+ <string> Hz</string>
+ </property>
+ <property name="maximum">
+ <number>2000</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="camdevice_combo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumContentsLength">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Camera settings (when available)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_36">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Width</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_41">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Height</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_37">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>FPS</string>
+ </property>
+ <property name="buddy">
+ <cstring>fps_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QPushButton" name="camera_settings">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Open</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QSpinBox" name="fov">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="suffix">
+ <string>°</string>
+ </property>
+ <property name="prefix">
+ <string/>
+ </property>
+ <property name="minimum">
+ <number>10</number>
+ </property>
+ <property name="maximum">
+ <number>90</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Device</string>
+ </property>
+ <property name="buddy">
+ <cstring>camdevice_combo</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSpinBox" name="res_y_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Desired capture height</string>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="maximum">
+ <number>2000</number>
+ </property>
+ <property name="singleStep">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="res_x_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Desired capture width</string>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="maximum">
+ <number>2000</number>
+ </property>
+ <property name="singleStep">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Diagonal field of view</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Debug (full size preview)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Min size</string>
+ </property>
+ <property name="buddy">
+ <cstring>mindiam_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="2">
+ <widget class="QSpinBox" name="mindiam_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Minimum point diameter</string>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QCheckBox" name="iCheckBoxAutoCenter">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QCheckBox" name="debug">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_13">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Auto center</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Max size</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="2">
+ <widget class="QSpinBox" name="spinDeadzone">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Size in pixels of half the edge defining deadzone squares around tracked points</string>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_12">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use P3P or AP3P for three and four points setup. Use EPNP or ITERATIVE for five points setup. Inconsistent configuration will result in undefined behavior.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Perspective-N-Point solver</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QComboBox" name="comboBoxSolvers">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Make sure you pick a solver supporting the number of marker you are using. For three points detection use either P3P or AP3P.</string>
+ </property>
+ <property name="currentText">
+ <string>ITERATIVE</string>
+ </property>
+ <item>
+ <property name="text">
+ <string>ITERATIVE</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>EPNP</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>P3P</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>DLS</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>UPNP</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>AP3P</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QLabel" name="labelDeadzone">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Deadzone</string>
+ </property>
+ <property name="buddy">
+ <cstring>maxdiam_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="2">
+ <widget class="QSpinBox" name="maxdiam_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Maximum point diameter</string>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_14">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Auto center timeout</string>
+ </property>
+ <property name="buddy">
+ <cstring>mindiam_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QSpinBox" name="iSpinBoxAutoCenterTimeout">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>If no valid pose can be determined after that much time the center pose will be used.</string>
+ </property>
+ <property name="suffix">
+ <string> ms</string>
+ </property>
+ <property name="maximum">
+ <number>3600000</number>
+ </property>
+ <property name="singleStep">
+ <number>500</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_4">
+ <attribute name="title">
+ <string>Model</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBoxCustomModelType">
+ <property name="title">
+ <string>Model type:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QRadioButton" name="iRadioButtonCustomModelThree">
+ <property name="text">
+ <string>Hat three vertices</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="iRadioButtonCustomModelFour">
+ <property name="text">
+ <string>Hat four vertices</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="iRadioButtonCustomModelFive">
+ <property name="text">
+ <string>Hat five vertices</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="iRadioButtonClipModelThree">
+ <property name="text">
+ <string>Clip three vertices</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Vertices: </string>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;X&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Y&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_11">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Z&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="iGroupBoxTop">
+ <property name="title">
+ <string>Top:</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexTopX">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexTopY">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexTopZ">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="iGroupBoxRight">
+ <property name="title">
+ <string>Right:</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexRightX">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexRightY">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexRightZ">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="iGroupBoxLeft">
+ <property name="title">
+ <string>Left:</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexLeftX">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexLeftY">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexLeftZ">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="iGroupBoxCenter">
+ <property name="title">
+ <string>Center:</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexCenterX">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexCenterY">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexCenterZ">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="iGroupBoxTopRight">
+ <property name="title">
+ <string>Top right:</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexTopRightX">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexTopRightY">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexTopRightZ">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="iGroupBoxTopLeft">
+ <property name="title">
+ <string>Top left:</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexTopLeftX">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexTopLeftY">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexTopLeftZ">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="iGroupBoxClipTop">
+ <property name="title">
+ <string>Clip top:</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexClipTopX">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexClipTopY">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexClipTopZ">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="iGroupBoxClipMiddle">
+ <property name="title">
+ <string>Clip middle:</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexClipMiddleX">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexClipMiddleY">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexClipMiddleZ">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="iGroupBoxClipBottom">
+ <property name="title">
+ <string>Clip bottom:</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexClipBottomX">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexClipBottomY">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="iSpinVertexClipBottomZ">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_3">
+ <attribute name="title">
+ <string>About</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_8">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Easy Tracker&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Stéphane Lenclud&lt;/span&gt;&lt;/p&gt;&lt;p&gt;See &lt;a href=&quot;https://github.com/opentrack/opentrack/wiki/Easy-Tracker&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#9999aa;&quot;&gt;documentation on GitHub&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label_35">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap>:/Resources/tracker-easy-logo.png</pixmap>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignBottom">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>camdevice_combo</tabstop>
+ <tabstop>res_x_spin</tabstop>
+ <tabstop>res_y_spin</tabstop>
+ <tabstop>fps_spin</tabstop>
+ <tabstop>fov</tabstop>
+ <tabstop>camera_settings</tabstop>
+ </tabstops>
+ <resources>
+ <include location="tracker_easy.qrc"/>
+ </resources>
+ <connections/>
+ <slots>
+ <slot>startEngineClicked()</slot>
+ <slot>stopEngineClicked()</slot>
+ <slot>cameraSettingsClicked()</slot>
+ </slots>
+</ui>
diff --git a/tracker-easy/tracker-easy.cpp b/tracker-easy/tracker-easy.cpp
new file mode 100644
index 00000000..7046a918
--- /dev/null
+++ b/tracker-easy/tracker-easy.cpp
@@ -0,0 +1,912 @@
+/* Copyright (c) 2019 Stephane Lenclud
+ *
+ * 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 "tracker-easy.h"
+#include "video/video-widget.hpp"
+#include "compat/math-imports.hpp"
+#include "compat/check-visible.hpp"
+#include "compat/sleep.hpp"
+#include "point-extractor.h"
+#include "cv/init.hpp"
+
+#include <QHBoxLayout>
+#include <QDebug>
+#include <QFile>
+#include <QCoreApplication>
+
+#include <opencv2/calib3d.hpp>
+#include <opencv2/highgui/highgui.hpp>
+
+#include <iostream>
+
+#ifdef __GNUC__
+# pragma GCC diagnostic ignored "-Wsign-conversion"
+# pragma GCC diagnostic ignored "-Wfloat-conversion"
+
+#endif
+
+#ifdef __clang__
+# pragma clang diagnostic ignored "-Wimplicit-float-conversion"
+#endif
+
+using namespace options;
+
+// Disable debug
+#define dbgout if (true) {} else std::cout << "\n" <<std::chrono::system_clock::now().time_since_epoch().count() << ": "
+//#define infout if (true) {} else std::cout
+// Enable debug
+//#define dbgout if (false) {} else std::cout
+#define infout if (false) {} else std::cout << "\n" << std::chrono::system_clock::now().time_since_epoch().count() << ": "
+
+// We need at least 3 vertices to be able to do anything
+const int KMinVertexCount = 3;
+
+
+
+namespace EasyTracker
+{
+
+ Tracker::Tracker() :
+ iSettings{ KModuleName },
+ iPreview{ preview_width, preview_height }
+ {
+ opencv_init();
+
+ connect(&*iSettings.b, &bundle_::saving, this, &Tracker::CheckCamera, Qt::DirectConnection);
+ connect(&*iSettings.b, &bundle_::reloading, this, &Tracker::CheckCamera, Qt::DirectConnection);
+
+ connect(&iSettings.fov, value_::value_changed<int>(), this, &Tracker::set_fov, Qt::DirectConnection);
+ set_fov(iSettings.fov);
+ // We could not get this working, nevermind
+ //connect(&iSettings.cam_fps, value_::value_changed<int>(), this, &Tracker::SetFps, Qt::DirectConnection);
+
+ // Make sure deadzones are updated whenever the settings are changed
+ connect(&iSettings.DeadzoneRectHalfEdgeSize, value_::value_changed<int>(), this, &Tracker::UpdateSettings, Qt::DirectConnection);
+
+ // Update point extractor whenever some of the settings it needs are changed
+ connect(&iSettings.iMinBlobSize, value_::value_changed<int>(), this, &Tracker::UpdateSettings, Qt::DirectConnection);
+ connect(&iSettings.iMaxBlobSize, value_::value_changed<int>(), this, &Tracker::UpdateSettings, Qt::DirectConnection);
+
+ // Make sure solver is updated whenever the settings are changed
+ connect(&iSettings.PnpSolver, value_::value_changed<int>(), this, &Tracker::UpdateSettings, Qt::DirectConnection);
+
+ // Debug
+ connect(&iSettings.debug, value_::value_changed<bool>(), this, &Tracker::UpdateSettings, Qt::DirectConnection);
+
+ // Make sure model is updated whenever it is changed
+ connect(&iSettings.iCustomModelThree, value_::value_changed<bool>(), this, &Tracker::UpdateModel, Qt::DirectConnection);
+ connect(&iSettings.iCustomModelFour, value_::value_changed<bool>(), this, &Tracker::UpdateModel, Qt::DirectConnection);
+ connect(&iSettings.iCustomModelFive, value_::value_changed<bool>(), this, &Tracker::UpdateModel, Qt::DirectConnection);
+
+ // Update model logic
+ #define UM(v) connect(&iSettings.v, value_::value_changed<int>(), this, &Tracker::UpdateModel, Qt::DirectConnection)
+ UM(iVertexTopX); UM(iVertexTopY); UM(iVertexTopZ);
+ UM(iVertexTopRightX); UM(iVertexTopRightY); UM(iVertexTopRightZ);
+ UM(iVertexTopLeftX); UM(iVertexTopLeftY); UM(iVertexTopLeftZ);
+ UM(iVertexRightX); UM(iVertexRightY); UM(iVertexRightZ);
+ UM(iVertexLeftX); UM(iVertexLeftY); UM(iVertexLeftZ);
+ UM(iVertexCenterX); UM(iVertexCenterY); UM(iVertexCenterZ);
+
+ UpdateModel();
+
+ UpdateSettings();
+
+ cv::namedWindow("Preview");
+ }
+
+ Tracker::~Tracker()
+ {
+ iThread.exit();
+ iThread.wait();
+
+ if (iDebug)
+ cv::destroyWindow("Preview");
+
+ if (camera)
+ {
+ QMutexLocker l(&camera_mtx);
+ camera->stop();
+ }
+ }
+
+
+ // Compute Euler angles from rotation matrix
+ void getEulerAngles(cv::Mat &rotCamerMatrix, cv::Vec3d &eulerAngles)
+ {
+ cv::Mat cameraMatrix, rotMatrix, transVect, rotMatrixX, rotMatrixY, rotMatrixZ;
+ double* _r = rotCamerMatrix.ptr<double>();
+ double projMatrix[12] = { _r[0],_r[1],_r[2],0,
+ _r[3],_r[4],_r[5],0,
+ _r[6],_r[7],_r[8],0 };
+
+ cv::decomposeProjectionMatrix(cv::Mat(3, 4, CV_64FC1, projMatrix),
+ cameraMatrix,
+ rotMatrix,
+ transVect,
+ rotMatrixX,
+ rotMatrixY,
+ rotMatrixZ,
+ eulerAngles);
+ }
+
+ ///
+ void Tracker::CreateCameraIntrinsicsMatrices()
+ {
+ // Create our camera matrix
+ iCameraMatrix(0, 0) = iCameraInfo.fx;
+ iCameraMatrix(1, 1) = iCameraInfo.fy;
+ iCameraMatrix(0, 2) = iCameraInfo.P_x;
+ iCameraMatrix(1, 2) = iCameraInfo.P_y;
+ iCameraMatrix(2, 2) = 1;
+
+ // Create distortion cooefficients
+ iDistCoeffsMatrix = cv::Matx<double, 8, 1>::zeros();
+ // As per OpenCV docs they should be thus: k1, k2, p1, p2, k3, k4, k5, k6
+ // 0 - Radial first order
+ // 1 - Radial second order
+ // 2 - Tangential first order
+ // 3 - Tangential second order
+ // 4 - Radial third order
+ // 5 - Radial fourth order
+ // 6 - Radial fifth order
+ // 7 - Radial sixth order
+ //
+ // SL: Using distortion coefficients in this way is breaking our face tracking output.
+ // Just disable them for now until we invest time and effort to work it out.
+ // For our face tracking use case not having proper distortion coefficients ain't a big deal anyway
+ // See issues #1141 and #1020
+ //for (unsigned k = 0; k < 8; k++)
+ // iDistCoeffsMatrix(k) = (double)iCameraInfo.dist_c[k];
+ }
+
+
+ void Tracker::MatchVertices(int& aTopIndex, int& aRightIndex, int& aLeftIndex, int& aCenterIndex, int& aTopRight, int& aTopLeft)
+ {
+ if (iModel.size() == 5)
+ {
+ MatchFiveVertices(aTopIndex, aRightIndex, aLeftIndex, aTopRight, aTopLeft);
+ }
+ else if (!iSettings.iClipModelThree)
+ {
+ MatchThreeOrFourVertices(aTopIndex, aRightIndex, aLeftIndex, aCenterIndex);
+ }
+ else
+ {
+ // Clip model
+ MatchClipVertices(aTopIndex, aRightIndex, aLeftIndex);
+ }
+ }
+
+
+ void Tracker::MatchFiveVertices(int& aTopIndex, int& aRightIndex, int& aLeftIndex, int& aTopRight, int& aTopLeft)
+ {
+ //Bitmap origin is top left
+ iTrackedPoints.clear();
+
+ int vertexIndices[] = { -1,-1,-1,-1,-1 };
+ std::vector<int> indices = { 0,1,2,3,4 };
+
+ // Tracked points must match the order of the object model points.
+ // Find top most point, that's the one with min Y as we assume our guy's head is not up side down
+ int minY = std::numeric_limits<int>::max();
+ for (int i = 0; i < (int)iPoints.size(); i++)
+ {
+ if (iPoints[i].y < minY)
+ {
+ minY = iPoints[i].y;
+ vertexIndices[VertexPosition::Top] = i;
+ }
+ }
+ indices.erase(std::find(indices.begin(), indices.end(), vertexIndices[VertexPosition::Top]));
+
+ // Find right most point
+ int maxX = 0;
+ for (int i = 0; i < (int)iPoints.size(); i++)
+ {
+ // Excluding top most point
+ if (i != vertexIndices[VertexPosition::Top] && iPoints[i].x > maxX)
+ {
+ maxX = iPoints[i].x;
+ vertexIndices[VertexPosition::Right] = i;
+ }
+ }
+ indices.erase(std::find(indices.begin(), indices.end(), vertexIndices[VertexPosition::Right]));
+
+ // Find left most point
+ int minX = std::numeric_limits<int>::max();
+ for (int i = 0; i < (int)iPoints.size(); i++)
+ {
+ // Excluding top most point and right most point
+ if (i != vertexIndices[VertexPosition::Top] && i != vertexIndices[VertexPosition::Right] && iPoints[i].x < minX)
+ {
+ minX = iPoints[i].x;
+ vertexIndices[VertexPosition::Left] = i;
+ }
+ }
+ indices.erase(std::find(indices.begin(), indices.end(), vertexIndices[VertexPosition::Left]));
+
+ // Check which of our two remaining points is on the left
+ int leftIndex = -1;
+ int rightIndex = -1;
+ if (iPoints[indices[0]].x > iPoints[indices[1]].x)
+ {
+ leftIndex = indices[1];
+ rightIndex = indices[0];
+ }
+ else
+ {
+ leftIndex = indices[0];
+ rightIndex = indices[1];
+ }
+
+ // Check which of the left points is at the top
+ if (iPoints[vertexIndices[VertexPosition::Left]].y < iPoints[leftIndex].y)
+ {
+ vertexIndices[VertexPosition::TopLeft] = vertexIndices[VertexPosition::Left];
+ vertexIndices[VertexPosition::Left] = leftIndex;
+ }
+ else
+ {
+ vertexIndices[VertexPosition::TopLeft] = leftIndex;
+ }
+
+ // Check which of the right points is at the top
+ if (iPoints[vertexIndices[VertexPosition::Right]].y < iPoints[rightIndex].y)
+ {
+ vertexIndices[VertexPosition::TopRight] = vertexIndices[VertexPosition::Right];
+ vertexIndices[VertexPosition::Right] = rightIndex;
+ }
+ else
+ {
+ vertexIndices[VertexPosition::TopRight] = rightIndex;
+ }
+
+
+ // Order matters, see UpdateModel function
+ iTrackedPoints.push_back(iPoints[vertexIndices[VertexPosition::Top]]);
+ iTrackedPoints.push_back(iPoints[vertexIndices[VertexPosition::Right]]);
+ iTrackedPoints.push_back(iPoints[vertexIndices[VertexPosition::Left]]);
+ iTrackedPoints.push_back(iPoints[vertexIndices[VertexPosition::TopRight]]);
+ iTrackedPoints.push_back(iPoints[vertexIndices[VertexPosition::TopLeft]]);
+
+ //
+ aTopIndex = vertexIndices[VertexPosition::Top];
+ aRightIndex = vertexIndices[VertexPosition::Right];
+ aLeftIndex = vertexIndices[VertexPosition::Left];
+ aTopRight = vertexIndices[VertexPosition::TopRight];
+ aTopLeft = vertexIndices[VertexPosition::TopLeft];
+
+
+ }
+
+
+ void Tracker::MatchThreeOrFourVertices(int& aTopIndex, int& aRightIndex, int& aLeftIndex, int& aCenterIndex)
+ {
+ //Bitmap origin is top left
+ iTrackedPoints.clear();
+ // Tracked points must match the order of the object model points.
+ // Find top most point, that's the one with min Y as we assume our guy's head is not up side down
+ int minY = std::numeric_limits<int>::max();
+ for (int i = 0; i < (int)iPoints.size(); i++)
+ {
+ if (iPoints[i].y < minY)
+ {
+ minY = iPoints[i].y;
+ aTopIndex = i;
+ }
+ }
+
+
+ int maxX = 0;
+
+ // Find right most point
+ for (int i = 0; i < (int)iPoints.size(); i++)
+ {
+ // Excluding top most point
+ if (i != aTopIndex && iPoints[i].x > maxX)
+ {
+ maxX = iPoints[i].x;
+ aRightIndex = i;
+ }
+ }
+
+ // Find left most point
+ int minX = std::numeric_limits<int>::max();
+ for (int i = 0; i < (int)iPoints.size(); i++)
+ {
+ // Excluding top most point and right most point
+ if (i != aTopIndex && i != aRightIndex && iPoints[i].x < minX)
+ {
+ aLeftIndex = i;
+ minX = iPoints[i].x;
+ }
+ }
+
+ // Find center point, the last one
+ for (int i = 0; i < (int)iPoints.size(); i++)
+ {
+ // Excluding the three points we already have
+ if (i != aTopIndex && i != aRightIndex && i != aLeftIndex)
+ {
+ aCenterIndex = i;
+ }
+ }
+
+ // Order matters
+ iTrackedPoints.push_back(iPoints[aTopIndex]);
+ iTrackedPoints.push_back(iPoints[aRightIndex]);
+ iTrackedPoints.push_back(iPoints[aLeftIndex]);
+ if (iModel.size() > iTrackedPoints.size())
+ {
+ // We are tracking more than 3 points
+ iTrackedPoints.push_back(iPoints[aCenterIndex]);
+ }
+ }
+
+ /**
+ */
+ void Tracker::MatchClipVertices(int& aTopIndex, int& aMiddleIndex, int& aBottomIndex)
+ {
+ //Bitmap origin is top left
+ iTrackedPoints.clear();
+ // Tracked points must match the order of the object model points.
+ // Find top most point, that's the one with min Y as we assume our guy's head is not up side down
+ int minY = std::numeric_limits<int>::max();
+ for (int i = 0; i < (int)iPoints.size(); i++)
+ {
+ if (iPoints[i].y < minY)
+ {
+ minY = iPoints[i].y;
+ aTopIndex = i;
+ }
+ }
+
+
+ int maxY = 0;
+
+ // Find bottom most point
+ for (int i = 0; i < (int)iPoints.size(); i++)
+ {
+ // Excluding top most point
+ if (i != aTopIndex && iPoints[i].y > maxY)
+ {
+ maxY = iPoints[i].y;
+ aBottomIndex = i;
+ }
+ }
+
+
+ // Find center point, the last one
+ for (int i = 0; i < (int)iPoints.size(); i++)
+ {
+ // Excluding the three points we already have
+ if (i != aTopIndex && i != aBottomIndex)
+ {
+ aMiddleIndex = i;
+ }
+ }
+
+ // Order matters
+ iTrackedPoints.push_back(iPoints[aTopIndex]);
+ iTrackedPoints.push_back(iPoints[aMiddleIndex]);
+ iTrackedPoints.push_back(iPoints[aBottomIndex]);
+ }
+
+
+
+ ///
+ ///
+ ///
+ void Tracker::ProcessFrame()
+ {
+ //infout << "ProcessFrame - begin";
+ QMutexLocker l(&iProcessLock);
+
+ // Create OpenCV matrix from our frame
+ // TODO: Assert channel size is one or two
+ iMatFrame = cv::Mat(iFrame.height, iFrame.width, CV_MAKETYPE((iFrame.channel_size == 2 ? CV_16U : CV_8U), iFrame.channels), iFrame.data, iFrame.stride);
+ iFrameCount++;
+
+ bool doPreview = check_is_visible();
+ if (doPreview)
+ {
+ iPreview = iMatFrame;
+ }
+
+ iPoints.clear();
+
+ // Do not attempt point extraction on a color buffer as it is running real slow and is useless anyway.
+ // If we are ever going to support color buffer we will need another implementation.
+ if (iFrame.channels == 1)
+ {
+ iPointExtractor.ExtractPoints(iMatFrame, (doPreview ? &iPreview.iFrameRgb : nullptr), (int)iModel.size(), iPoints);
+ }
+
+
+ const bool success = iPoints.size() >= iModel.size() && iModel.size() >= KMinVertexCount;
+
+ int topPointIndex = -1;
+ int rightPointIndex = -1;
+ int leftPointIndex = -1;
+ int centerPointIndex = -1;
+ int topRightPointIndex = -1;
+ int topLeftPointIndex = -1;
+
+ if (success)
+ {
+ // Lets match our 3D vertices with our image 2D points
+ MatchVertices(topPointIndex, rightPointIndex, leftPointIndex, centerPointIndex, topRightPointIndex, topLeftPointIndex);
+
+ bool movedEnough = true;
+ // Check if we moved enough since last time we were here
+ // This is our deadzone management
+ if (iDeadzoneHalfEdge != 0 // Check if deazones are enabled
+ && iTrackedRects.size() == iTrackedPoints.size())
+ {
+ movedEnough = false;
+ for (size_t i = 0; i < iTrackedPoints.size(); i++)
+ {
+ if (!iTrackedRects[i].contains(iTrackedPoints[i]))
+ {
+ movedEnough = true;
+ break;
+ }
+ }
+ }
+
+ if (!movedEnough)
+ {
+ // We are in a dead zone
+ // However we still have tracking so make sure we don't auto center
+ QMutexLocker lock(&iDataLock);
+ iBestTime.start();
+ }
+ else
+ {
+ // Build deadzone rectangles if needed
+ iTrackedRects.clear();
+ if (iDeadzoneHalfEdge != 0) // Check if deazones are enabled
+ {
+ for (const cv::Point2f& pt : iTrackedPoints)
+ {
+ cv::Rect rect(pt - cv::Point2f(iDeadzoneHalfEdge, iDeadzoneHalfEdge), cv::Size(iDeadzoneEdge, iDeadzoneEdge));
+ iTrackedRects.push_back(rect);
+ }
+ }
+
+ dbgout << "Object: " << iModel << "\n";
+ dbgout << "Points: " << iTrackedPoints << "\n";
+
+ iAngles.clear();
+ iBestSolutionIndex = -1;
+ // Solve P3P problem with OpenCV
+ int solutionCount = 0;
+ if (iModel.size() == 3)
+ {
+ solutionCount = cv::solveP3P(iModel, iTrackedPoints, iCameraMatrix, iDistCoeffsMatrix, iRotations, iTranslations, iSolver);
+ }
+ else
+ {
+ //Guess extrinsic boolean is only for ITERATIVE method, it will be set to false for all other method
+ cv::Mat rotation, translation;
+ // Init only needed for iterative, it's also useless as it is
+ rotation = cv::Mat::zeros(3, 1, CV_64FC1);
+ translation = cv::Mat::zeros(3, 1, CV_64FC1);
+ rotation.setTo(cv::Scalar(0));
+ translation.setTo(cv::Scalar(0));
+ /////
+ iRotations.clear();
+ iTranslations.clear();
+ bool solved = cv::solvePnP(iModel, iTrackedPoints, iCameraMatrix, iDistCoeffsMatrix, rotation, translation, true, iSolver );
+ if (solved)
+ {
+ solutionCount = 1;
+ iRotations.push_back(rotation);
+ iTranslations.push_back(translation);
+ }
+ }
+
+ // Reset best solution index
+ iBestSolutionIndex = -1;
+
+ if (solutionCount > 0)
+ {
+ dbgout << "Solution count: " << solutionCount << "\n";
+ int minPitch = std::numeric_limits<int>::max();
+ // Find the solution we want amongst all possible ones
+ for (int i = 0; i < solutionCount; i++)
+ {
+ dbgout << "Translation:\n";
+ dbgout << iTranslations.at(i);
+ dbgout << "\n";
+ dbgout << "Rotation:\n";
+ //dbgout << rvecs.at(i);
+ cv::Mat rotationCameraMatrix;
+ cv::Rodrigues(iRotations[i], rotationCameraMatrix);
+ cv::Vec3d angles;
+ getEulerAngles(rotationCameraMatrix, angles);
+ iAngles.push_back(angles);
+
+ // Check if pitch is closest to zero
+ int absolutePitch = (int)std::abs(angles[0]);
+ if (minPitch > absolutePitch)
+ {
+ // The solution with pitch closest to zero is the one we want
+ minPitch = absolutePitch;
+ iBestSolutionIndex = i;
+ }
+
+ dbgout << angles;
+ dbgout << "\n";
+ }
+
+ dbgout << "\n";
+ }
+
+ if (iBestSolutionIndex != -1)
+ {
+ // Best translation
+ cv::Vec3d translation = iTranslations[iBestSolutionIndex];
+ // Best angles
+ cv::Vec3d angles = iAngles[iBestSolutionIndex];
+
+ // Pass solution through our kalman filter
+ iKf.Update(translation[0], translation[1], translation[2], angles[2], angles[0], angles[1]);
+
+ // Check if our solution makes sense
+ // For now, just discard solutions with extrem pitch
+ if (std::abs(angles[0]) > 50) //TODO: Put that in settings
+ {
+ infout << "WARNING: discarding solution!";
+ iBadSolutionCount++;
+ }
+ else
+ {
+ iGoodSolutionCount++;
+ // We succeded in finding a solution to our PNP problem
+ ever_success.store(true, std::memory_order_relaxed);
+
+ // Send solution data back to main thread
+ QMutexLocker l2(&iDataLock);
+ iBestAngles = angles;
+ iBestTranslation = translation;
+ iBestTime.start();
+ }
+
+ }
+ }
+ }
+
+ if (doPreview)
+ {
+ double qualityIndex = 1 - (iGoodSolutionCount!=0?(double)iBadSolutionCount / (double)iGoodSolutionCount:0);
+ std::ostringstream ss;
+ ss << "FPS: " << iFps << "/" << iSkippedFps << " QI: " << qualityIndex;
+ iPreview.DrawInfo(ss.str());
+
+ //Color is BGR
+ if (topPointIndex != -1)
+ {
+ // Render a cross to indicate which point is the head
+ static const cv::Scalar color(0, 255, 255); // Yellow
+ iPreview.DrawCross(iPoints[topPointIndex],color);
+ }
+
+ if (rightPointIndex != -1)
+ {
+ static const cv::Scalar color(255, 0, 255); // Pink
+ iPreview.DrawCross(iPoints[rightPointIndex], color);
+ }
+
+ if (leftPointIndex != -1)
+ {
+ static const cv::Scalar color(255, 0, 0); // Blue
+ iPreview.DrawCross(iPoints[leftPointIndex], color);
+ }
+
+ if (centerPointIndex != -1)
+ {
+ static const cv::Scalar color(0, 255, 0); // Green
+ iPreview.DrawCross(iPoints[centerPointIndex], color);
+ }
+
+ if (topRightPointIndex != -1)
+ {
+ static const cv::Scalar color(0, 0, 255); // Red
+ iPreview.DrawCross(iPoints[topRightPointIndex], color);
+ }
+
+ if (topLeftPointIndex != -1)
+ {
+ static const cv::Scalar color(255, 255, 0); // Cyan
+ iPreview.DrawCross(iPoints[topLeftPointIndex], color);
+ }
+
+
+ // Render our deadzone rects
+ for (const cv::Rect& rect : iTrackedRects)
+ {
+ cv::rectangle(iPreview.iFrameRgb,rect,cv::Scalar(255,0,0));
+ }
+
+ // Show full size preview pop-up
+ if (iDebug)
+ {
+ cv::imshow("Preview", iPreview.iFrameRgb);
+ cv::waitKey(1);
+ }
+
+ // Update preview widget
+ widget->update_image(iPreview.get_bitmap());
+
+ auto[w, h] = widget->preview_size();
+ if (w != preview_width || h != preview_height)
+ {
+ // Resize preivew if widget size has changed
+ preview_width = w; preview_height = h;
+ iPreview = Preview(w, h);
+ }
+ }
+ else
+ {
+ // No preview, destroy preview pop-up
+ if (iDebug)
+ {
+ cv::destroyWindow("Preview");
+ }
+ }
+
+ dbgout << "Frame time:" << iTimer.elapsed_seconds();
+ //infout << "ProcessFrame - end";
+ }
+
+ ///
+ ///
+ ///
+ void Tracker::Tick()
+ {
+ if (CheckCamera())
+ {
+ // Camera was just started, skipping that frame as it was causing a deadlock on our process mutex in ProcessFrame
+ // In fact it looked like ProcessFrame was called twice without completing.
+ // That has something to do with the ticker interval being changed after the camera is started.
+ return;
+ }
+
+ iTimer.start();
+
+ bool new_frame = false;
+ {
+ QMutexLocker l(&camera_mtx);
+
+ if (camera)
+ {
+ std::tie(iFrame, new_frame) = camera->get_frame();
+ }
+
+ }
+
+ if (new_frame)
+ {
+ ProcessFrame();
+ }
+ else
+ {
+ iSkippedFrameCount++;
+ }
+
+ // Compute FPS
+ double elapsed = iFpsTimer.elapsed_seconds();
+ if (elapsed >= 1.0)
+ {
+ iFps = iFrameCount / elapsed;
+ iSkippedFps = iSkippedFrameCount / elapsed;
+ iFrameCount = 0;
+ iSkippedFrameCount = 0;
+ iFpsTimer.start();
+ }
+
+ }
+
+ /// @return True if camera was just started, false otherwise.
+ bool Tracker::CheckCamera()
+ {
+ QMutexLocker l(&camera_mtx);
+
+ if (camera->is_open())
+ {
+ return false;
+ }
+
+ iCameraInfo.fps = iSettings.cam_fps;
+ iCameraInfo.width = iSettings.cam_res_x;
+ iCameraInfo.height = iSettings.cam_res_y;
+
+ bool res = camera->start(iCameraInfo);
+ //portable::sleep(5000);
+
+ // We got our camera intrinsics, create corresponding matrices
+ CreateCameraIntrinsicsMatrices();
+
+ // If ever the camera implementation provided an FPS now is the time to apply it
+ DoSetFps(iCameraInfo.fps);
+
+ return res;
+ }
+
+ void Tracker::set_fov(int value)
+ {
+ (void)value;
+ //QMutexLocker l(&camera_mtx);
+
+ }
+
+ // Calling this from another thread than the one it belongs too after it's started somehow breaks our timer
+ void Tracker::SetFps(int aFps)
+ {
+ QMutexLocker l(&camera_mtx);
+ DoSetFps(aFps);
+ }
+
+ void Tracker::DoSetFps(int aFps)
+ {
+ // Aplly FPS to timer
+ iTicker.setInterval(1000 / aFps + 1);
+
+ // Reset Kalman filter
+ //int nStates = 18; // the number of states
+ //int nMeasurements = 6; // the number of measured states
+ //int nInputs = 0; // the number of control actions
+ //double dt = 0.125; // time between measurements (1/FPS)
+ double dt = 1000.0 / aFps;
+ iKf.Init(18, 6, 0, dt);
+ }
+
+
+ ///
+ /// Create our model from settings specifications
+ ///
+ void Tracker::UpdateModel()
+ {
+ infout << "Update model - begin";
+
+ QMutexLocker lock(&iProcessLock);
+ // Construct the points defining the object we want to detect based on settings.
+ // We are converting them from millimeters to centimeters.
+ // TODO: Need to support clip too. That's cap only for now.
+ iModel.clear();
+
+ if (!iSettings.iClipModelThree)
+ {
+ iModel.push_back(cv::Point3f(iSettings.iVertexTopX / 10.0, iSettings.iVertexTopY / 10.0, iSettings.iVertexTopZ / 10.0)); // Top
+ iModel.push_back(cv::Point3f(iSettings.iVertexRightX / 10.0, iSettings.iVertexRightY / 10.0, iSettings.iVertexRightZ / 10.0)); // Right
+ iModel.push_back(cv::Point3f(iSettings.iVertexLeftX / 10.0, iSettings.iVertexLeftY / 10.0, iSettings.iVertexLeftZ / 10.0)); // Left
+
+ if (iSettings.iCustomModelFour)
+ {
+ iModel.push_back(cv::Point3f(iSettings.iVertexCenterX / 10.0, iSettings.iVertexCenterY / 10.0, iSettings.iVertexCenterZ / 10.0)); // Center
+ }
+ else if (iSettings.iCustomModelFive)
+ {
+ iModel.push_back(cv::Point3f(iSettings.iVertexTopRightX / 10.0, iSettings.iVertexTopRightY / 10.0, iSettings.iVertexTopRightZ / 10.0)); // Top Right
+ iModel.push_back(cv::Point3f(iSettings.iVertexTopLeftX / 10.0, iSettings.iVertexTopLeftY / 10.0, iSettings.iVertexTopLeftZ / 10.0)); // Top Left
+ }
+ }
+ else
+ {
+ // Clip model type
+ iModel.push_back(cv::Point3f(iSettings.iVertexClipTopX / 10.0, iSettings.iVertexClipTopY / 10.0, iSettings.iVertexClipTopZ / 10.0)); // Top
+ iModel.push_back(cv::Point3f(iSettings.iVertexClipMiddleX / 10.0, iSettings.iVertexClipMiddleY / 10.0, iSettings.iVertexClipMiddleZ / 10.0)); // Middle
+ iModel.push_back(cv::Point3f(iSettings.iVertexClipBottomX / 10.0, iSettings.iVertexClipBottomY / 10.0, iSettings.iVertexClipBottomZ / 10.0)); // Bottom
+ }
+
+ infout << "Update model - end";
+ }
+
+ ///
+ /// Take a copy of the settings needed by our thread to avoid deadlocks
+ ///
+ void Tracker::UpdateSettings()
+ {
+ infout << "Update Setting - begin";
+ QMutexLocker l(&iProcessLock);
+ iPointExtractor.UpdateSettings();
+ iSolver = iSettings.PnpSolver;
+ iDeadzoneHalfEdge = iSettings.DeadzoneRectHalfEdgeSize;
+ iDeadzoneEdge = iDeadzoneHalfEdge * 2;
+ iTrackedRects.clear();
+ iDebug = iSettings.debug;
+ infout << "Update Setting - end";
+ }
+
+ ///
+ module_status Tracker::start_tracker(QFrame* video_frame)
+ {
+ // Check that we support that solver
+ if (iSolver!=cv::SOLVEPNP_P3P && iSolver != cv::SOLVEPNP_AP3P && iModel.size()==3)
+ {
+ return module_status("Error: Solver not supported use either P3P or AP3P.");
+ }
+
+ // Create our camera
+ camera = video::make_camera(iSettings.camera_name);
+
+ if (!camera)
+ return error(QStringLiteral("Can't open camera %1").arg(iSettings.camera_name));
+
+ //video_frame->setAttribute(Qt::WA_NativeWindow);
+ widget = std::make_unique<video_widget>(video_frame);
+ layout = std::make_unique<QHBoxLayout>(video_frame);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->addWidget(&*widget);
+ video_frame->setLayout(&*layout);
+ //video_widget->resize(video_frame->width(), video_frame->height());
+ video_frame->show();
+
+ // Precise timer is needed otherwise the interval is not really respected
+ iTicker.setTimerType(Qt::PreciseTimer);
+ SetFps(iSettings.cam_fps);
+ iTicker.moveToThread(&iThread);
+ // Connect timer timeout signal to our tick slot
+ connect(&iTicker, SIGNAL(timeout()), SLOT(Tick()), Qt::DirectConnection);
+ // Start our timer once our thread is started
+ iTicker.connect(&iThread, SIGNAL(started()), SLOT(start()));
+ iFpsTimer.start(); // Kick off our FPS counter
+ iThread.setObjectName("EasyTrackerThread");
+ iThread.setPriority(QThread::HighPriority); // Do we really want that?
+ iThread.start();
+
+ return {};
+ }
+
+ //
+ void FeedData(double* aData, const cv::Vec3d& aAngles, const cv::Vec3d& aTranslation)
+ {
+ aData[Yaw] = aAngles[1];
+ aData[Pitch] = aAngles[0];
+ aData[Roll] = aAngles[2];
+ aData[TX] = aTranslation[0];
+ aData[TY] = aTranslation[1];
+ aData[TZ] = aTranslation[2];
+ }
+
+ //
+ // That's called around 250 times per second.
+ // Therefore we better not do anything here other than provide current data.
+ //
+ void Tracker::data(double* aData)
+ {
+ if (ever_success.load(std::memory_order_relaxed))
+ {
+ // Get data back from tracker thread
+ QMutexLocker l(&iDataLock);
+ // If there was no new data recently then we provide center data.
+ // Basically, if our user remove her hat, we will go back to center position until she puts it back on.
+ if (iSettings.iAutoCenter && iBestTime.elapsed_ms() > iSettings.iAutoCenterTimeout)
+ {
+ // Reset to center until we get new data
+ FeedData(aData, iCenterAngles, iCenterTranslation);
+ }
+ else
+ {
+ // We got valid data, provide it
+ FeedData(aData, iBestAngles, iBestTranslation);
+ }
+ }
+ }
+
+ bool Tracker::center()
+ {
+ QMutexLocker l(&iDataLock);
+ iCenterTranslation = iBestTranslation;
+ iCenterAngles = iBestAngles;
+ // Returning false tells the pipeline we want to use the default center behaviour
+ return false;
+ }
+
+
+}
diff --git a/tracker-easy/tracker-easy.h b/tracker-easy/tracker-easy.h
new file mode 100644
index 00000000..4510fc7d
--- /dev/null
+++ b/tracker-easy/tracker-easy.h
@@ -0,0 +1,168 @@
+/* Copyright (c) 2019 Stephane Lenclud
+ *
+ * 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 "cv/numeric.hpp"
+#include "video/video-widget.hpp"
+#include "video/camera.hpp"
+#include "compat/timer.hpp"
+
+
+#include "preview.h"
+#include "settings.h"
+#include "point-extractor.h"
+#include "kalman-filter-pose.h"
+
+#include <atomic>
+#include <memory>
+#include <vector>
+
+#include <opencv2/core.hpp>
+#include <opencv2/video/tracking.hpp>
+
+#include <QThread>
+#include <QMutex>
+#include <QLayout>
+
+namespace EasyTracker
+{
+
+ namespace VertexPosition
+ {
+ enum Type
+ {
+ Top = 0,
+ Right,
+ Left,
+ TopRight,
+ TopLeft,
+ Center
+ };
+ }
+
+ static const QString KModuleName = "tracker-easy";
+
+ class Dialog;
+
+ using namespace numeric_types;
+
+ struct Tracker : public QObject, ITracker
+ {
+ Q_OBJECT
+ public:
+ friend class Dialog;
+
+ explicit Tracker();
+ ~Tracker() override;
+
+ // From ITracker
+ module_status start_tracker(QFrame* parent_window) override;
+ void data(double* data) override;
+ bool center() override;
+
+ private slots:
+ void Tick();
+
+
+ private:
+ void UpdateModel();
+ void CreateCameraIntrinsicsMatrices();
+ void ProcessFrame();
+ void MatchVertices(int& aTopIndex, int& aRightIndex, int& aLeftIndex, int& aCenterIndex, int& aTopRight, int& aTopLeft);
+ void MatchThreeOrFourVertices(int& aTopIndex, int& aRightIndex, int& aLeftIndex, int& aCenterIndex);
+ void MatchFiveVertices(int& aTopIndex, int& aRightIndex, int& aLeftIndex, int& aTopRight, int& aTopLeft);
+ void MatchClipVertices(int& aTopIndex, int& aMiddleIndex, int& aBottomIndex);
+
+
+ //
+
+ bool CheckCamera();
+ void set_fov(int value);
+ void SetFps(int aFps);
+ void DoSetFps(int aFps);
+ void UpdateSettings();
+
+ QMutex camera_mtx;
+ QThread iThread;
+ QTimer iTicker;
+
+ Settings iSettings;
+
+ std::unique_ptr<QLayout> layout;
+ std::vector<cv::Point> iPoints;
+
+ int preview_width = 320, preview_height = 240;
+
+ PointExtractor iPointExtractor;
+
+ std::unique_ptr<video::impl::camera> camera;
+ video::impl::camera::info iCameraInfo;
+ std::unique_ptr<video_widget> widget;
+
+ video::frame iFrame;
+ cv::Mat iMatFrame;
+ Preview iPreview;
+
+ std::atomic<bool> ever_success = false;
+ mutable QMutex iProcessLock, iDataLock;
+
+ //// Copy the settings need by our thread to avoid dead locks
+ // Deadzone
+ int iDeadzoneEdge=0;
+ int iDeadzoneHalfEdge=0;
+ // Solver
+ int iSolver = cv::SOLVEPNP_P3P;
+ bool iDebug = false;
+ ////
+
+ // Statistics
+ Timer iTimer;
+ Timer iFpsTimer;
+ int iFrameCount = 0;
+ int iSkippedFrameCount = 0;
+ int iFps = 0;
+ int iSkippedFps = 0;
+ uint iBadSolutionCount = 0;
+ uint iGoodSolutionCount = 0;
+
+ //
+ KalmanFilterPose iKf;
+
+ // Vertices defining the model we are tracking
+ std::vector<cv::Point3f> iModel;
+ // Bitmap points corresponding to model vertices
+ std::vector<cv::Point2f> iTrackedPoints;
+
+ std::vector<cv::Rect> iTrackedRects;
+
+ // Intrinsics camera matrix
+ cv::Matx33d iCameraMatrix { cv::Matx33d::zeros() };
+ // Intrinsics distortion coefficients as a matrix
+ cv::Matx<double, 8, 1> iDistCoeffsMatrix;
+ // Translation solutions
+ std::vector<cv::Mat> iTranslations;
+ // Rotation solutions
+ std::vector<cv::Mat> iRotations;
+ // Angle solutions, pitch, yaw, roll, in this order
+ std::vector<cv::Vec3d> iAngles;
+ // The index of our best solution in the above arrays
+ int iBestSolutionIndex = -1;
+ // Best translation
+ cv::Vec3d iBestTranslation;
+ // Best angles
+ cv::Vec3d iBestAngles;
+ // Time at which we found our last best solution
+ Timer iBestTime;
+ // Center translation
+ cv::Vec3d iCenterTranslation = {0,0,0};
+ // Center angles
+ cv::Vec3d iCenterAngles = { 0,0,0 };
+ };
+
+}
diff --git a/tracker-easy/tracker_easy.qrc b/tracker-easy/tracker_easy.qrc
new file mode 100644
index 00000000..b7d1a896
--- /dev/null
+++ b/tracker-easy/tracker_easy.qrc
@@ -0,0 +1,9 @@
+<RCC>
+ <qresource prefix="/">
+ <file>Resources/easy-tracker-logo.png</file>
+ <file>Resources/cap_front.png</file>
+ <file>Resources/cap_side.png</file>
+ <file>Resources/clip_front.png</file>
+ <file>Resources/clip_side.png</file>
+ </qresource>
+</RCC>
diff --git a/tracker-eyeware-beam/CMakeLists.txt b/tracker-eyeware-beam/CMakeLists.txt
new file mode 100644
index 00000000..e041c131
--- /dev/null
+++ b/tracker-eyeware-beam/CMakeLists.txt
@@ -0,0 +1,27 @@
+# The Eyeware Beam SDK can be found at https://beam.eyeware.tech/developers/
+# The latest version can be downloaded at https://eyewarecistorage.blob.core.windows.net/beam-sdk/BeamSDK-Windows64-1.1.0.zip
+set(SDK_EYEWARE_BEAM "" CACHE PATH "Eyeware Beam SDK path")
+if(WIN32 AND SDK_EYEWARE_BEAM)
+ if(MSVC)
+ add_compile_options(-EHsc)
+ endif()
+ otr_module(tracker-eyeware-beam)
+
+ if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
+ set(arch "x86")
+ else()
+ set(arch "x64")
+ endif()
+
+ target_include_directories(${self} SYSTEM PRIVATE "${SDK_EYEWARE_BEAM}/API/cpp/include")
+ target_link_directories(${self} PRIVATE "${SDK_EYEWARE_BEAM}/API/cpp/lib/${arch}")
+ set(dll "${SDK_EYEWARE_BEAM}/API/cpp/lib/${arch}/tracker_client.dll")
+ set(lib tracker_client.lib)
+
+ #message(${self})
+ #message(${dll})
+ #message(${lib})
+
+ target_link_libraries(${self} ${lib})
+ install(FILES ${dll} DESTINATION ${opentrack-libexec})
+endif()
diff --git a/tracker-eyeware-beam/eyeware_beam.cpp b/tracker-eyeware-beam/eyeware_beam.cpp
new file mode 100644
index 00000000..f48b5d4d
--- /dev/null
+++ b/tracker-eyeware-beam/eyeware_beam.cpp
@@ -0,0 +1,116 @@
+/* Copyright (c) 2023 Eyeware Tech SA https://www.eyeware.tech
+ *
+ * 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 "eyeware_beam.h"
+
+#include <QMutexLocker>
+
+static constexpr double rad_to_deg = 180.0 / M_PI;
+static constexpr double m_to_cm = 100.0;
+static constexpr double epsilon = 0.000001;
+
+eyeware_beam_tracker::eyeware_beam_tracker()
+{
+}
+
+eyeware_beam_tracker::~eyeware_beam_tracker()
+{
+ QMutexLocker lck(&mtx);
+ release_tracker_instance(tracker_client);
+ tracker_client = nullptr;
+}
+
+module_status eyeware_beam_tracker::start_tracker(QFrame* videoframe)
+{
+ QMutexLocker lck(&mtx);
+ try
+ {
+ tracker_client = create_tracker_instance("127.0.0.1", 12010);
+ }
+ catch (...)
+ {
+ return error("Eyeware Beam initialization has failed");
+ }
+
+ return status_ok();
+}
+
+void eyeware_beam_tracker::extract_translation(const eyeware::Vector3D& t,
+ double& translation_x_cm,
+ double& translation_y_cm,
+ double& translation_z_cm)
+{
+ translation_x_cm = +t.x * m_to_cm;
+ translation_y_cm = -t.y * m_to_cm;
+ translation_z_cm = +t.z * m_to_cm;
+}
+
+void eyeware_beam_tracker::extract_rotation_angles(const eyeware::Matrix3x3& R,
+ double& pitch_deg,
+ double& roll_deg,
+ double& yaw_deg)
+{
+ double r00 = static_cast<double>(R[0][0]);
+ double r01 = static_cast<double>(R[0][1]);
+ double r02 = static_cast<double>(R[0][2]);
+ double r10 = static_cast<double>(R[1][0]);
+ double r11 = static_cast<double>(R[1][1]);
+ double r12 = static_cast<double>(R[1][2]);
+ double r20 = static_cast<double>(R[2][0]);
+ double r21 = static_cast<double>(R[2][1]);
+ double r22 = static_cast<double>(R[2][2]);
+
+ double dy = std::sqrt(r00 * r00 + r10 * r10);
+ last_yaw_deg = -std::atan2(-r20, dy) * rad_to_deg;
+ last_roll_deg = 0.0;
+ if (dy > epsilon)
+ {
+ last_pitch_deg = -std::atan2(r21, r22) * rad_to_deg;
+ last_roll_deg = +std::atan2(r10, r00) * rad_to_deg;
+ }
+ else
+ {
+ last_pitch_deg = -std::atan2(-r12, r11) * rad_to_deg;
+ }
+}
+
+void eyeware_beam_tracker::data(double *data)
+{
+ QMutexLocker lck(&mtx);
+
+ if (connected(tracker_client))
+ {
+ eyeware::HeadPoseInfo head_pose_info = get_head_pose_info(tracker_client);
+ if (!head_pose_info.is_lost)
+ {
+ extract_translation(head_pose_info.transform.translation, last_translation_x_cm,
+ last_translation_y_cm, last_translation_z_cm);
+ extract_rotation_angles(head_pose_info.transform.rotation, last_pitch_deg, last_roll_deg, last_yaw_deg);
+ }
+ }
+
+ data[TX] = last_translation_x_cm;
+ data[TY] = last_translation_y_cm;
+ data[TZ] = last_translation_z_cm;
+ data[Yaw] = last_yaw_deg;
+ data[Pitch] = last_pitch_deg;
+ data[Roll] = last_roll_deg;
+}
+
+eyeware_beam_dialog::eyeware_beam_dialog()
+{
+ ui.setupUi(this);
+
+ connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
+}
+
+void eyeware_beam_dialog::doOK()
+{
+ close();
+}
+
+OPENTRACK_DECLARE_TRACKER(eyeware_beam_tracker, eyeware_beam_dialog, eyeware_beam_metadata)
diff --git a/tracker-eyeware-beam/eyeware_beam.h b/tracker-eyeware-beam/eyeware_beam.h
new file mode 100644
index 00000000..9ebf1f3c
--- /dev/null
+++ b/tracker-eyeware-beam/eyeware_beam.h
@@ -0,0 +1,70 @@
+/* Copyright (c) 2023 Eyeware Tech SA https://www.eyeware.tech
+ *
+ * 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_eyeware_beam.h"
+
+#include "eyeware/tracker_client.h"
+
+#include <QObject>
+#include <QMutex>
+
+class eyeware_beam_tracker : public QObject, public ITracker
+{
+ Q_OBJECT
+
+public:
+ eyeware_beam_tracker();
+ ~eyeware_beam_tracker() override;
+ module_status start_tracker(QFrame* frame) override;
+ void data(double *data) override;
+
+private:
+ void extract_translation(const eyeware::Vector3D& t,
+ double& translation_x_cm,
+ double& translation_y_cm,
+ double& translation_z_cm);
+ void extract_rotation_angles(const eyeware::Matrix3x3& R, double& pitch_deg, double& roll_deg, double& yaw_deg);
+
+ eyeware::TrackerClient* tracker_client = nullptr;
+
+ QMutex mtx;
+
+ double last_pitch_deg = 0.0;
+ double last_roll_deg = 0.0;
+ double last_yaw_deg = 0.0;
+ double last_translation_x_cm = 0.0;
+ double last_translation_y_cm = 0.0;
+ double last_translation_z_cm = 0.0;
+};
+
+class eyeware_beam_dialog : public ITrackerDialog
+{
+ Q_OBJECT
+
+public:
+ eyeware_beam_dialog();
+ void register_tracker(ITracker * x) override { tracker = static_cast<eyeware_beam_tracker*>(x); }
+ void unregister_tracker() override { tracker = nullptr; }
+
+private:
+ Ui::eyeware_beam_ui ui;
+ eyeware_beam_tracker* tracker = nullptr;
+
+private Q_SLOTS:
+ void doOK();
+};
+
+class eyeware_beam_metadata : public Metadata
+{
+ Q_OBJECT
+ QString name() override { return QString("Eyeware Beam"); }
+ QIcon icon() override { return QIcon(":/images/eyeware_beam_logo.png"); }
+};
diff --git a/tracker-eyeware-beam/eyeware_beam.qrc b/tracker-eyeware-beam/eyeware_beam.qrc
new file mode 100644
index 00000000..ae20865e
--- /dev/null
+++ b/tracker-eyeware-beam/eyeware_beam.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/images">
+ <file>eyeware_beam_logo.png</file>
+ </qresource>
+</RCC>
diff --git a/tracker-eyeware-beam/eyeware_beam.ui b/tracker-eyeware-beam/eyeware_beam.ui
new file mode 100644
index 00000000..475db6a0
--- /dev/null
+++ b/tracker-eyeware-beam/eyeware_beam.ui
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>eyeware_beam_ui</class>
+ <widget class="QWidget" name="eyeware_beam_ui">
+ <property name="windowModality">
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>433</width>
+ <height>180</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Eyeware Beam</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="eyeware_beam.qrc">
+ <normaloff>:/images/eyeware_beam_logo.png</normaloff>:/images/eyeware_beam_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="QFrame" name="frame_2">
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <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>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Please make sure the Eyeware Beam application is running and tracking is active.&lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;To download, visit &lt;a href=&quot;https://beam.eyeware.tech/opentrack&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;https://beam.eyeware.tech/opentrack&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </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::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="eyeware_beam.qrc"/>
+ </resources>
+ <connections/>
+ <slots>
+ <slot>startEngineClicked()</slot>
+ <slot>stopEngineClicked()</slot>
+ <slot>cameraSettingsClicked()</slot>
+ </slots>
+</ui>
diff --git a/tracker-eyeware-beam/eyeware_beam_logo.png b/tracker-eyeware-beam/eyeware_beam_logo.png
new file mode 100644
index 00000000..6a611cac
--- /dev/null
+++ b/tracker-eyeware-beam/eyeware_beam_logo.png
Binary files differ
diff --git a/tracker-eyeware-beam/lang/nl_NL.ts b/tracker-eyeware-beam/lang/nl_NL.ts
new file mode 100644
index 00000000..d70f58ed
--- /dev/null
+++ b/tracker-eyeware-beam/lang/nl_NL.ts
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>eyeware_beam_ui</name>
+ <message>
+ <source>Eyeware Beam</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Please make sure the Eyeware Beam application is running and tracking is active.&lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;To download, visit &lt;a href=&quot;https://beam.eyeware.tech/opentrack&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;https://beam.eyeware.tech/opentrack&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-eyeware-beam/lang/ru_RU.ts b/tracker-eyeware-beam/lang/ru_RU.ts
new file mode 100644
index 00000000..0bff47ae
--- /dev/null
+++ b/tracker-eyeware-beam/lang/ru_RU.ts
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>eyeware_beam_ui</name>
+ <message>
+ <source>Eyeware Beam</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Please make sure the Eyeware Beam application is running and tracking is active.&lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;To download, visit &lt;a href=&quot;https://beam.eyeware.tech/opentrack&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;https://beam.eyeware.tech/opentrack&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-eyeware-beam/lang/stub.ts b/tracker-eyeware-beam/lang/stub.ts
new file mode 100644
index 00000000..c64ff83e
--- /dev/null
+++ b/tracker-eyeware-beam/lang/stub.ts
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>eyeware_beam_ui</name>
+ <message>
+ <source>Eyeware Beam</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Please make sure the Eyeware Beam application is running and tracking is active.&lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;To download, visit &lt;a href=&quot;https://beam.eyeware.tech/opentrack&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;https://beam.eyeware.tech/opentrack&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-eyeware-beam/lang/zh_CN.ts b/tracker-eyeware-beam/lang/zh_CN.ts
new file mode 100644
index 00000000..aed44317
--- /dev/null
+++ b/tracker-eyeware-beam/lang/zh_CN.ts
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>eyeware_beam_ui</name>
+ <message>
+ <source>Eyeware Beam</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Please make sure the Eyeware Beam application is running and tracking is active.&lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;To download, visit &lt;a href=&quot;https://beam.eyeware.tech/opentrack&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;https://beam.eyeware.tech/opentrack&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-freepie-udp/ftnoir_tracker_freepie-udp.cpp b/tracker-freepie-udp/ftnoir_tracker_freepie-udp.cpp
index a93a22cb..3950af0c 100644
--- a/tracker-freepie-udp/ftnoir_tracker_freepie-udp.cpp
+++ b/tracker-freepie-udp/ftnoir_tracker_freepie-udp.cpp
@@ -25,6 +25,8 @@ void tracker_freepie::run() {
} data;
#pragma pack(pop)
+ static_assert(sizeof(data) == 50);
+
enum F
{
flag_Raw = 1 << 0,
@@ -38,9 +40,9 @@ void tracker_freepie::run() {
{
int order[] =
{
- clamp(s.idx_x, 0, 2),
- clamp(s.idx_y, 0, 2),
- clamp(s.idx_z, 0, 2)
+ std::clamp(*s.idx_x, 0, 2),
+ std::clamp(*s.idx_y, 0, 2),
+ std::clamp(*s.idx_z, 0, 2)
};
double orient[3] = {0, 0, 0};
@@ -49,7 +51,7 @@ void tracker_freepie::run() {
while (sock.hasPendingDatagrams())
{
using t = decltype(data);
- t tmp {0,0, {0,0,0, 0,0,0, 0,0,0, 0,0,0}};
+ t tmp {};
(void) sock.readDatagram(reinterpret_cast<char*>(&tmp), sizeof(data));
int flags = tmp.flags & F::Mask;
@@ -61,12 +63,13 @@ void tracker_freepie::run() {
continue;
case flag_Raw | flag_Orient:
for (int i = 0; i < 3; i++)
- orient[i] = tmp.fl[i+9];
+ orient[i] = (double)tmp.fl[i+9];
break;
case flag_Orient:
for (int i = 0; i < 3; i++)
- orient[i] = tmp.fl[i];
+ orient[i] = (double)tmp.fl[i];
break;
+ default: goto fail;
}
filled = true;
@@ -100,6 +103,7 @@ void tracker_freepie::run() {
pose[Yaw + i] = r2d * orient[axis] + add;
}
}
+fail:
usleep(4000);
}
}
diff --git a/tracker-freepie-udp/ftnoir_tracker_freepie-udp.h b/tracker-freepie-udp/ftnoir_tracker_freepie-udp.h
index cc5d0be0..522f3b80 100644
--- a/tracker-freepie-udp/ftnoir_tracker_freepie-udp.h
+++ b/tracker-freepie-udp/ftnoir_tracker_freepie-udp.h
@@ -61,8 +61,9 @@ private slots:
class meta_freepie : public Metadata
{
-public:
- QString name() { return otr_tr("FreePIE UDP receiver"); }
+ Q_OBJECT
+
+ QString name() { return tr("FreePIE UDP receiver"); }
QIcon icon() { return QIcon(":/glovepie.png"); }
};
diff --git a/tracker-freepie-udp/lang/de_DE.ts b/tracker-freepie-udp/lang/de_DE.ts
new file mode 100644
index 00000000..27f3bc39
--- /dev/null
+++ b/tracker-freepie-udp/lang/de_DE.ts
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>UI_freepie_udp_dialog</name>
+ <message>
+ <source>Tracker settings</source>
+ <translation>Tracker-Einstellungen</translation>
+ </message>
+ <message>
+ <source>UDP port</source>
+ <translation>UDP-Port</translation>
+ </message>
+ <message>
+ <source>Axis order</source>
+ <translation>Achsen-Reihenfolge</translation>
+ </message>
+ <message>
+ <source>output yaw</source>
+ <translation>Gieren-Ausgabe</translation>
+ </message>
+ <message>
+ <source>input yaw</source>
+ <translation>Gieren-Eingabe</translation>
+ </message>
+ <message>
+ <source>input pitch</source>
+ <translation>Nicken-Eingabe</translation>
+ </message>
+ <message>
+ <source>input roll</source>
+ <translation>Rollen-Eingabe</translation>
+ </message>
+ <message>
+ <source>output pitch</source>
+ <translation>Nicken-Ausgabe</translation>
+ </message>
+ <message>
+ <source>output roll</source>
+ <translation>Rollen-Ausgabe</translation>
+ </message>
+ <message>
+ <source>Add to axis</source>
+ <translation>Zur Achse hinzufügen</translation>
+ </message>
+ <message>
+ <source>yaw</source>
+ <translation>Gieren</translation>
+ </message>
+ <message>
+ <source>0</source>
+ <translation>0</translation>
+ </message>
+ <message>
+ <source>+90</source>
+ <translation>+90</translation>
+ </message>
+ <message>
+ <source>-90</source>
+ <translation>-90</translation>
+ </message>
+ <message>
+ <source>+180</source>
+ <translation>+180</translation>
+ </message>
+ <message>
+ <source>-180</source>
+ <translation>-180</translation>
+ </message>
+ <message>
+ <source>pitch</source>
+ <translation>Nicken</translation>
+ </message>
+ <message>
+ <source>roll</source>
+ <translation>Rollen</translation>
+ </message>
+</context>
+<context>
+ <name>meta_freepie</name>
+ <message>
+ <source>FreePIE UDP receiver</source>
+ <translation>FreePIE-UDP-Empfänger</translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-freepie-udp/lang/nl_NL.ts b/tracker-freepie-udp/lang/nl_NL.ts
index 940da926..bf739c4d 100644
--- a/tracker-freepie-udp/lang/nl_NL.ts
+++ b/tracker-freepie-udp/lang/nl_NL.ts
@@ -76,4 +76,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>meta_freepie</name>
+ <message>
+ <source>FreePIE UDP receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-freepie-udp/lang/ru_RU.ts b/tracker-freepie-udp/lang/ru_RU.ts
index 004a0103..1a088f44 100644
--- a/tracker-freepie-udp/lang/ru_RU.ts
+++ b/tracker-freepie-udp/lang/ru_RU.ts
@@ -76,4 +76,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>meta_freepie</name>
+ <message>
+ <source>FreePIE UDP receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-freepie-udp/lang/stub.ts b/tracker-freepie-udp/lang/stub.ts
index 53710990..1072784a 100644
--- a/tracker-freepie-udp/lang/stub.ts
+++ b/tracker-freepie-udp/lang/stub.ts
@@ -76,4 +76,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>meta_freepie</name>
+ <message>
+ <source>FreePIE UDP receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-freepie-udp/lang/zh_CN.ts b/tracker-freepie-udp/lang/zh_CN.ts
index 53710990..b886451c 100644
--- a/tracker-freepie-udp/lang/zh_CN.ts
+++ b/tracker-freepie-udp/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>UI_freepie_udp_dialog</name>
<message>
@@ -76,4 +76,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>meta_freepie</name>
+ <message>
+ <source>FreePIE UDP receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-fusion/fusion.cpp b/tracker-fusion/fusion.cpp
index 98e2465e..fba38d3d 100644
--- a/tracker-fusion/fusion.cpp
+++ b/tracker-fusion/fusion.cpp
@@ -6,23 +6,24 @@
* notice appear in all copies.
*/
+#undef NDEBUG
+
#include "fusion.h"
#include "compat/library-path.hpp"
#include <QDebug>
#include <QMessageBox>
#include <QApplication>
+#include <cassert>
-static const QString own_name = QStringLiteral("fusion");
+static const char* own_name = "fusion";
static auto get_modules()
{
- return Modules(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH);
+ return Modules(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH, dylib_load_quiet);
}
-fusion_tracker::fusion_tracker()
-{
-}
+fusion_tracker::fusion_tracker() = default;
fusion_tracker::~fusion_tracker()
{
@@ -32,13 +33,6 @@ fusion_tracker::~fusion_tracker()
rot_dylib = nullptr;
pos_dylib = nullptr;
-
- if (other_frame)
- {
- if (other_frame->layout())
- delete other_frame->layout();
- other_frame = nullptr;
- }
}
const QString& fusion_tracker::caption()
@@ -110,7 +104,6 @@ module_status fusion_tracker::start_tracker(QFrame* frame)
if (frame->layout() == nullptr)
{
status = rot_tracker->start_tracker(frame);
- other_frame = nullptr;
if (!status.is_ok())
{
err = rot_dylib->name + QStringLiteral(":\n ") + status.error;
@@ -119,16 +112,10 @@ module_status fusion_tracker::start_tracker(QFrame* frame)
}
else
{
- other_frame->setVisible(false);
other_frame->setFixedSize(320, 240); // XXX magic frame size
+ other_frame->setVisible(false);
- rot_tracker->start_tracker(other_frame.get());
-
- if (other_frame->layout() == nullptr)
- other_frame = nullptr;
- else
- other_frame->hide();
-
+ rot_tracker->start_tracker(&*other_frame);
}
end:
@@ -159,8 +146,8 @@ fusion_dialog::fusion_dialog()
connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
- ui.rot_tracker->addItem("");
- ui.pos_tracker->addItem("");
+ ui.rot_tracker->addItem({});
+ ui.pos_tracker->addItem({});
Modules libs = get_modules();
diff --git a/tracker-fusion/fusion.h b/tracker-fusion/fusion.h
index 6b0e0b1e..daadf27d 100644
--- a/tracker-fusion/fusion.h
+++ b/tracker-fusion/fusion.h
@@ -1,13 +1,16 @@
#pragma once
+
#include "api/plugin-api.hpp"
#include "api/plugin-support.hpp"
+#include "options/options.hpp"
+using namespace options;
+
+#include <memory>
+
#include <QObject>
#include <QFrame>
#include <QCoreApplication>
-#include "options/options.hpp"
-using namespace options;
-
struct fusion_settings final : opts
{
value<QVariant> rot_tracker_name, pos_tracker_name;
@@ -21,7 +24,7 @@ class fusion_tracker : public QObject, public ITracker
double rot_tracker_data[6] {}, pos_tracker_data[6] {};
- std::unique_ptr<QFrame> other_frame;
+ std::unique_ptr<QFrame> other_frame { std::make_unique<QFrame>() };
std::shared_ptr<dylib> rot_dylib, pos_dylib;
std::shared_ptr<ITracker> rot_tracker, pos_tracker;
@@ -51,8 +54,9 @@ private slots:
class fusion_metadata : public Metadata
{
-public:
- QString name() { return otr_tr("Fusion"); }
+ Q_OBJECT
+
+ QString name() { return tr("Fusion"); }
QIcon icon() { return QIcon(":/images/fusion-tracker-logo.png"); }
};
diff --git a/tracker-fusion/lang/de_DE.ts b/tracker-fusion/lang/de_DE.ts
new file mode 100644
index 00000000..3019b655
--- /dev/null
+++ b/tracker-fusion/lang/de_DE.ts
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>fusion_dialog</name>
+ <message>
+ <source>Fusion tracker only works when distinct trackers are selected for rotation and position.</source>
+ <translation>Der Fusion-Tracker funktioniert nur, wenn für Rotation und Position unterschiedliche Tracker ausgewählt wurden.</translation>
+ </message>
+</context>
+<context>
+ <name>fusion_metadata</name>
+ <message>
+ <source>Fusion</source>
+ <translation>Fusion</translation>
+ </message>
+</context>
+<context>
+ <name>fusion_tracker</name>
+ <message>
+ <source>Fusion tracker</source>
+ <translation>Fusion-Tracker</translation>
+ </message>
+ <message>
+ <source>Trackers not selected.</source>
+ <translation>Keine Tracker ausgewählt.</translation>
+ </message>
+ <message>
+ <source>Select different trackers for rotation and position.</source>
+ <translation>Wähle unterschiedliche Tracker für Rotation und Position.</translation>
+ </message>
+</context>
+<context>
+ <name>fusion_ui</name>
+ <message>
+ <source>Fusion</source>
+ <translation>Fusion</translation>
+ </message>
+ <message>
+ <source>Set distinct trackers for rotation and position input.</source>
+ <translation>Wähle unterschiedliche Tracker für Rotations- und Positionseingabe.</translation>
+ </message>
+ <message>
+ <source>Configure the trackers on the main window. It&apos;s required that they&apos;re both distinct, and both are set to something.</source>
+ <translation>Konfiguriere die Tracker im Hauptfenster. Es ist unbedingt erforderlich, dass zwei unterschiedliche Tracker ausgewählt werden, und beides zumindest irgendeinen Tracker auswählt.</translation>
+ </message>
+ <message>
+ <source>Rotation</source>
+ <translation>Rotation</translation>
+ </message>
+ <message>
+ <source>Position</source>
+ <translation>Position</translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-fusion/lang/nl_NL.ts b/tracker-fusion/lang/nl_NL.ts
index b19667b0..3ad3efec 100644
--- a/tracker-fusion/lang/nl_NL.ts
+++ b/tracker-fusion/lang/nl_NL.ts
@@ -9,6 +9,13 @@
</message>
</context>
<context>
+ <name>fusion_metadata</name>
+ <message>
+ <source>Fusion</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>fusion_tracker</name>
<message>
<source>Fusion tracker</source>
diff --git a/tracker-fusion/lang/ru_RU.ts b/tracker-fusion/lang/ru_RU.ts
index 1555f7ad..9453b167 100644
--- a/tracker-fusion/lang/ru_RU.ts
+++ b/tracker-fusion/lang/ru_RU.ts
@@ -9,6 +9,13 @@
</message>
</context>
<context>
+ <name>fusion_metadata</name>
+ <message>
+ <source>Fusion</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>fusion_tracker</name>
<message>
<source>Fusion tracker</source>
diff --git a/tracker-fusion/lang/stub.ts b/tracker-fusion/lang/stub.ts
index fd5cb84f..7aed6201 100644
--- a/tracker-fusion/lang/stub.ts
+++ b/tracker-fusion/lang/stub.ts
@@ -9,6 +9,13 @@
</message>
</context>
<context>
+ <name>fusion_metadata</name>
+ <message>
+ <source>Fusion</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>fusion_tracker</name>
<message>
<source>Fusion tracker</source>
diff --git a/tracker-fusion/lang/zh_CN.ts b/tracker-fusion/lang/zh_CN.ts
index fd5cb84f..e1345e4c 100644
--- a/tracker-fusion/lang/zh_CN.ts
+++ b/tracker-fusion/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>fusion_dialog</name>
<message>
@@ -9,6 +9,13 @@
</message>
</context>
<context>
+ <name>fusion_metadata</name>
+ <message>
+ <source>Fusion</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>fusion_tracker</name>
<message>
<source>Fusion tracker</source>
diff --git a/tracker-hatire/CMakeLists.txt b/tracker-hatire/CMakeLists.txt
index 39431724..01dd52b9 100644
--- a/tracker-hatire/CMakeLists.txt
+++ b/tracker-hatire/CMakeLists.txt
@@ -1,5 +1,8 @@
if(Qt5SerialPort_FOUND)
otr_module(tracker-hatire)
- target_link_libraries(opentrack-tracker-hatire ${Qt5SerialPort_LIBRARIES})
- target_include_directories(opentrack-tracker-hatire SYSTEM PUBLIC ${Qt5SerialPort_INCLUDE_DIRS})
+ target_link_libraries(${self} ${Qt5SerialPort_LIBRARIES})
+ target_include_directories(${self} SYSTEM PUBLIC ${Qt5SerialPort_INCLUDE_DIRS})
+ if (WIN32 OR APPLE)
+ otr_install_lib(Qt5::SerialPort .)
+ endif()
endif()
diff --git a/tracker-hatire/ftnoir_hatcontrols.ui b/tracker-hatire/ftnoir_hatcontrols.ui
index c9a449b1..758d8e51 100644
--- a/tracker-hatire/ftnoir_hatcontrols.ui
+++ b/tracker-hatire/ftnoir_hatcontrols.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>389</width>
- <height>528</height>
+ <height>497</height>
</rect>
</property>
<property name="minimumSize">
@@ -103,7 +103,7 @@
<bool>false</bool>
</property>
<property name="sizeAdjustPolicy">
- <enum>QComboBox::AdjustToMinimumContentsLength</enum>
+ <enum>QComboBox::AdjustToContents</enum>
</property>
<property name="modelColumn">
<number>0</number>
@@ -129,38 +129,6 @@
</property>
</widget>
</item>
- <item row="1" column="1">
- <widget class="QCheckBox" name="serial_bug_workaround">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>6</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Buggy serial port uses too much CPU</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_6">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>3</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximumSize">
- <size>
- <width>65536</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string>Workaround</string>
- </property>
- </widget>
- </item>
</layout>
</widget>
</item>
@@ -1168,16 +1136,6 @@ p, li { white-space: pre-wrap; }
<string>Serial Parameters</string>
</property>
<layout class="QGridLayout" name="gridLayout_8">
- <item row="0" column="1">
- <widget class="QComboBox" name="QCB_Serial_baudRate">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
<item row="3" column="1">
<widget class="QComboBox" name="QCB_Serial_stopBits">
<property name="sizePolicy">
@@ -1195,6 +1153,13 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="baudRateLabel">
+ <property name="text">
+ <string>BaudRate:</string>
+ </property>
+ </widget>
+ </item>
<item row="2" column="1">
<widget class="QComboBox" name="QCB_Serial_parity">
<property name="sizePolicy">
@@ -1205,13 +1170,10 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
- <item row="4" column="1">
- <widget class="QComboBox" name="QCB_Serial_flowControl">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <item row="1" column="0">
+ <widget class="QLabel" name="dataBitsLabel">
+ <property name="text">
+ <string>Data bits</string>
</property>
</widget>
</item>
@@ -1222,6 +1184,16 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="QCB_Serial_baudRate">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
<item row="2" column="0">
<widget class="QLabel" name="parityLabel">
<property name="text">
@@ -1229,15 +1201,18 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
- <item row="0" column="0">
- <widget class="QLabel" name="baudRateLabel">
- <property name="text">
- <string>BaudRate:</string>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="QCB_Serial_dataBits">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
</widget>
</item>
- <item row="1" column="1">
- <widget class="QComboBox" name="QCB_Serial_dataBits">
+ <item row="4" column="1">
+ <widget class="QComboBox" name="QCB_Serial_flowControl">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -1246,10 +1221,23 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QLabel" name="dataBitsLabel">
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_6">
<property name="text">
- <string>Data bits</string>
+ <string>DTR</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QCheckBox" name="QCB_Serial_dtr">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
</property>
</widget>
</item>
@@ -1403,6 +1391,7 @@ p, li { white-space: pre-wrap; }
</layout>
</widget>
<tabstops>
+ <tabstop>tabWidget</tabstop>
<tabstop>cbSerialPort</tabstop>
<tabstop>btnZero</tabstop>
<tabstop>btnReset</tabstop>
@@ -1425,24 +1414,27 @@ p, li { white-space: pre-wrap; }
<tabstop>cb_z</tabstop>
<tabstop>chkInvertZ</tabstop>
<tabstop>le_cmd_init</tabstop>
+ <tabstop>spb_BeforeInit</tabstop>
<tabstop>le_cmd_start</tabstop>
+ <tabstop>spb_BeforeStart</tabstop>
<tabstop>spb_AfterStart</tabstop>
<tabstop>le_cmd_stop</tabstop>
<tabstop>le_cmd_center</tabstop>
<tabstop>le_cmd_zero</tabstop>
- <tabstop>le_cmd_reset</tabstop>
<tabstop>spb_Fps</tabstop>
+ <tabstop>le_cmd_reset</tabstop>
<tabstop>cb_Endian</tabstop>
<tabstop>QCB_Serial_baudRate</tabstop>
<tabstop>QCB_Serial_dataBits</tabstop>
<tabstop>QCB_Serial_parity</tabstop>
<tabstop>QCB_Serial_stopBits</tabstop>
<tabstop>QCB_Serial_flowControl</tabstop>
+ <tabstop>QCB_Serial_dtr</tabstop>
+ <tabstop>btn_icone</tabstop>
+ <tabstop>chkEnableLogging</tabstop>
<tabstop>lineSend</tabstop>
<tabstop>btnSend</tabstop>
<tabstop>pteINFO</tabstop>
- <tabstop>tabWidget</tabstop>
- <tabstop>btn_icone</tabstop>
</tabstops>
<resources>
<include location="ftnoir_hat.qrc"/>
diff --git a/tracker-hatire/ftnoir_tracker_hat.cpp b/tracker-hatire/ftnoir_tracker_hat.cpp
index 9ac44cf2..9948c30e 100644
--- a/tracker-hatire/ftnoir_tracker_hat.cpp
+++ b/tracker-hatire/ftnoir_tracker_hat.cpp
@@ -15,23 +15,15 @@
hatire::hatire()
{
- HAT.Rot[0]=0;
- HAT.Rot[1]=0;
- HAT.Rot[2]=0;
- HAT.Trans[0]=0;
- HAT.Trans[1]=0;
- HAT.Trans[2]=0;
-
- Begin.append((unsigned char) 0xAA);
- Begin.append((unsigned char) 0xAA);
- End.append((unsigned char) 0x55);
- End.append((unsigned char) 0x55);
-}
-hatire::~hatire()
-{
+ Begin.append((unsigned char) 0xAA);
+ Begin.append((unsigned char) 0xAA);
+ End.append((unsigned char) 0x55);
+ End.append((unsigned char) 0x55);
}
+hatire::~hatire() = default;
+
//send RESET to Arduino
void hatire::reset()
{
@@ -130,29 +122,26 @@ void hatire::data(double *data)
}
for (unsigned k = 0; k < 3; k++)
- HAT.Rot[k] = clamp(HAT.Rot[k], -180, 180);
+ HAT.Rot[k] = std::clamp(HAT.Rot[k], -180.f, 180.f);
const struct
{
+ double& place;
+ float input;
bool enable;
bool sign;
- float input;
- double& place;
} spec[] =
{
- { s.EnableX, s.InvertX, HAT.Trans[s.XAxis], data[TX] },
- { s.EnableY, s.InvertY, HAT.Trans[s.YAxis], data[TY] },
- { s.EnableZ, s.InvertZ, HAT.Trans[s.ZAxis], data[TZ] },
- { s.EnableYaw, s.InvertYaw, HAT.Rot[s.YawAxis], data[Yaw] },
- { s.EnablePitch, s.InvertPitch, HAT.Rot[s.PitchAxis], data[Pitch] },
- { s.EnableRoll, s.InvertRoll, HAT.Rot[s.RollAxis], data[Roll] },
+ { data[TX], HAT.Trans[s.XAxis], s.EnableX, s.InvertX, },
+ { data[TY], HAT.Trans[s.YAxis], s.EnableY, s.InvertY, },
+ { data[TZ], HAT.Trans[s.ZAxis], s.EnableZ, s.InvertZ, },
+ { data[Yaw], HAT.Rot[s.YawAxis], s.EnableYaw, s.InvertYaw },
+ { data[Pitch], HAT.Rot[s.PitchAxis], s.EnablePitch, s.InvertPitch },
+ { data[Roll], HAT.Rot[s.RollAxis], s.EnableRoll, s.InvertRoll },
};
- for (unsigned i = 0; i < std::size(spec); i++)
- {
- auto& k = spec[i];
- k.place = (k.sign ? -1 : 1) * (k.enable ? k.input : 0);
- }
+ for (auto& k : spec)
+ k.place = (k.sign ? -1 : 1) * (k.enable ? (double)k.input : 0);
}
#include "ftnoir_tracker_hat_dialog.h"
diff --git a/tracker-hatire/ftnoir_tracker_hat.h b/tracker-hatire/ftnoir_tracker_hat.h
index 0f4b2c9c..16e39c7d 100644
--- a/tracker-hatire/ftnoir_tracker_hat.h
+++ b/tracker-hatire/ftnoir_tracker_hat.h
@@ -20,34 +20,35 @@ class hatire : public QObject, public ITracker
public:
hatire();
- ~hatire();
+ ~hatire() override;
- module_status start_tracker(QFrame*);
- void data(double *data);
+ module_status start_tracker(QFrame*) override;
+ void data(double *data) override;
//void center();
//bool notifyZeroed();
void reset();
- void get_info( int *tps );
+ void get_info(int *tps);
void serial_info();
void send_serial_command(const QByteArray& x);
hatire_thread t;
+
private:
- TArduinoData ArduinoData, HAT;
+ TArduinoData ArduinoData {}, HAT {};
QByteArray Begin;
QByteArray End;
TrackerSettings s;
- int frame_cnt;
-
- std::atomic<int> CptError;
+ int frame_cnt = 0;
- static inline QByteArray to_latin1(const QString& str) { return str.toLatin1(); }
+ std::atomic<int> CptError { 0 };
};
class hatire_metadata : public Metadata
{
- QString name() { return otr_tr("Hatire Arduino"); }
- QIcon icon() { return QIcon(":/images/hat.png"); }
+ Q_OBJECT
+
+ QString name() override { return tr("Hatire Arduino"); }
+ QIcon icon() override { return QIcon(":/images/hat.png"); }
};
diff --git a/tracker-hatire/ftnoir_tracker_hat_dialog.cpp b/tracker-hatire/ftnoir_tracker_hat_dialog.cpp
index 6fb17361..7cede5ca 100644
--- a/tracker-hatire/ftnoir_tracker_hat_dialog.cpp
+++ b/tracker-hatire/ftnoir_tracker_hat_dialog.cpp
@@ -32,6 +32,10 @@ dialog_hatire::dialog_hatire() : theTracker(nullptr), timer(this)
ui.QCB_Serial_baudRate->addItem(QLatin1String("38400"),QSerialPort::Baud38400);
ui.QCB_Serial_baudRate->addItem(QLatin1String("57600"),QSerialPort:: Baud57600);
ui.QCB_Serial_baudRate->addItem(QLatin1String("115200"),QSerialPort::Baud115200);
+ ui.QCB_Serial_baudRate->addItem(QLatin1String("230400"),(QSerialPort::BaudRate)230400);
+ ui.QCB_Serial_baudRate->addItem(QLatin1String("250000"),(QSerialPort::BaudRate)250000);
+ ui.QCB_Serial_baudRate->addItem(QLatin1String("500000"),(QSerialPort::BaudRate)500000);
+ ui.QCB_Serial_baudRate->addItem(QLatin1String("1000000"),(QSerialPort::BaudRate)1000000);
ui.QCB_Serial_dataBits->clear();
ui.QCB_Serial_dataBits->addItem(QLatin1String("5"), QSerialPort::Data5);
@@ -91,6 +95,7 @@ dialog_hatire::dialog_hatire() : theTracker(nullptr), timer(this)
tie_setting(s.DelaySeq, ui.spb_AfterStart);
tie_setting(s.BigEndian, ui.cb_Endian);
+ tie_setting(s.pDTR, ui.QCB_Serial_dtr);
tie_setting(s.pBaudRate, ui.QCB_Serial_baudRate);
tie_setting(s.pDataBits, ui.QCB_Serial_dataBits);
@@ -98,8 +103,6 @@ dialog_hatire::dialog_hatire() : theTracker(nullptr), timer(this)
tie_setting(s.pParity, ui.QCB_Serial_parity);
tie_setting(s.pStopBits, ui.QCB_Serial_stopBits);
- tie_setting(s.serial_bug_workaround, ui.serial_bug_workaround);
-
tie_setting(s.QSerialPortName, ui.cbSerialPort);
connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
@@ -118,8 +121,7 @@ dialog_hatire::dialog_hatire() : theTracker(nullptr), timer(this)
//connect(ui.lineSend,SIGNAL(keyPressEvent),this,SLOT(on_lineSend_returnPressed()) );
}
-dialog_hatire::~dialog_hatire() {
-}
+dialog_hatire::~dialog_hatire() = default;
void dialog_hatire::Initialize(QWidget *parent)
{
@@ -180,8 +182,8 @@ void dialog_hatire::poll_tracker_info()
int frame_cnt;
theTracker->get_info(&frame_cnt);
- ui.lab_vtps->setText(QString::number(frame_cnt*(1000/last_time.elapsed())));
- last_time.restart();
+ ui.lab_vtps->setText(QString::number(frame_cnt*(1000/last_time.elapsed_ms())));
+ last_time.start();
}
}
diff --git a/tracker-hatire/ftnoir_tracker_hat_dialog.h b/tracker-hatire/ftnoir_tracker_hat_dialog.h
index 7577445c..c578f22f 100644
--- a/tracker-hatire/ftnoir_tracker_hat_dialog.h
+++ b/tracker-hatire/ftnoir_tracker_hat_dialog.h
@@ -1,12 +1,12 @@
#pragma once
#include "api/plugin-api.hpp"
+#include "compat/timer.hpp"
#include "ftnoir_tracker_hat_settings.h"
#include "ftnoir_tracker_hat.h"
#include "ui_ftnoir_hatcontrols.h"
#include <QObject>
#include <QWidget>
-#include <QTime>
#include <QTimer>
#include <QMessageBox>
#include <QMetaType>
@@ -25,7 +25,7 @@ public:
private:
Ui::UIHATControls ui;
hatire *theTracker;
- QTime last_time;
+ Timer last_time;
public slots:
void WriteMsgInfo(const QByteArray &MsgInfo);
diff --git a/tracker-hatire/ftnoir_tracker_hat_settings.h b/tracker-hatire/ftnoir_tracker_hat_settings.h
index 7e9f7244..b17042c7 100644
--- a/tracker-hatire/ftnoir_tracker_hat_settings.h
+++ b/tracker-hatire/ftnoir_tracker_hat_settings.h
@@ -22,7 +22,7 @@ struct TrackerSettings : opts
value<int> DelayInit, DelayStart, DelaySeq;
- value<bool> BigEndian, EnableLogging, serial_bug_workaround;
+ value<bool> BigEndian, EnableLogging, pDTR;
value<QString> QSerialPortName;
@@ -63,7 +63,7 @@ struct TrackerSettings : opts
DelaySeq(b, "after-start-delay", 0),
BigEndian(b, "is-big-endian", false),
EnableLogging(b, "enable-logging", false),
- serial_bug_workaround(b, "serial-bug-workaround", false),
+ pDTR(b, "data-terminal-ready", false),
QSerialPortName(b, "serial-port-name", ""),
pBaudRate(b, "baud-rate", QSerialPort::Baud115200),
pDataBits(b, "data-bits", QSerialPort::Data8),
diff --git a/tracker-hatire/lang/nl_NL.ts b/tracker-hatire/lang/nl_NL.ts
index 616e4f9b..af8cbd2c 100644
--- a/tracker-hatire/lang/nl_NL.ts
+++ b/tracker-hatire/lang/nl_NL.ts
@@ -16,14 +16,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Buggy serial port uses too much CPU</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Workaround</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Zero</source>
<translation type="unfinished"></translation>
</message>
@@ -277,6 +269,10 @@ p, li { white-space: pre-wrap; }
<source>Enable logging to diagnostic file</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>DTR</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>dialog_hatire</name>
@@ -305,6 +301,13 @@ p, li { white-space: pre-wrap; }
</message>
</context>
<context>
+ <name>hatire_metadata</name>
+ <message>
+ <source>Hatire Arduino</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>hatire_thread</name>
<message>
<source>Timeout during writing command</source>
diff --git a/tracker-hatire/lang/ru_RU.ts b/tracker-hatire/lang/ru_RU.ts
index c27a8615..16052702 100644
--- a/tracker-hatire/lang/ru_RU.ts
+++ b/tracker-hatire/lang/ru_RU.ts
@@ -16,14 +16,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Buggy serial port uses too much CPU</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Workaround</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Zero</source>
<translation type="unfinished"></translation>
</message>
@@ -277,6 +269,10 @@ p, li { white-space: pre-wrap; }
<source>Enable logging to diagnostic file</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>DTR</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>dialog_hatire</name>
@@ -305,6 +301,13 @@ p, li { white-space: pre-wrap; }
</message>
</context>
<context>
+ <name>hatire_metadata</name>
+ <message>
+ <source>Hatire Arduino</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>hatire_thread</name>
<message>
<source>Timeout during writing command</source>
diff --git a/tracker-hatire/lang/stub.ts b/tracker-hatire/lang/stub.ts
index 5ccf7fe1..90c8e616 100644
--- a/tracker-hatire/lang/stub.ts
+++ b/tracker-hatire/lang/stub.ts
@@ -16,14 +16,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Buggy serial port uses too much CPU</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Workaround</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Zero</source>
<translation type="unfinished"></translation>
</message>
@@ -277,6 +269,10 @@ p, li { white-space: pre-wrap; }
<source>Enable logging to diagnostic file</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>DTR</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>dialog_hatire</name>
@@ -305,6 +301,13 @@ p, li { white-space: pre-wrap; }
</message>
</context>
<context>
+ <name>hatire_metadata</name>
+ <message>
+ <source>Hatire Arduino</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>hatire_thread</name>
<message>
<source>Timeout during writing command</source>
diff --git a/tracker-hatire/lang/zh_CN.ts b/tracker-hatire/lang/zh_CN.ts
index 5ccf7fe1..d80cfa28 100644
--- a/tracker-hatire/lang/zh_CN.ts
+++ b/tracker-hatire/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>UIHATControls</name>
<message>
@@ -16,14 +16,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Buggy serial port uses too much CPU</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Workaround</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Zero</source>
<translation type="unfinished"></translation>
</message>
@@ -277,6 +269,10 @@ p, li { white-space: pre-wrap; }
<source>Enable logging to diagnostic file</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>DTR</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>dialog_hatire</name>
@@ -305,6 +301,13 @@ p, li { white-space: pre-wrap; }
</message>
</context>
<context>
+ <name>hatire_metadata</name>
+ <message>
+ <source>Hatire Arduino</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>hatire_thread</name>
<message>
<source>Timeout during writing command</source>
diff --git a/tracker-hatire/thread.cpp b/tracker-hatire/thread.cpp
index 6025d74c..7d61a90a 100644
--- a/tracker-hatire/thread.cpp
+++ b/tracker-hatire/thread.cpp
@@ -1,13 +1,15 @@
#include "thread.hpp"
-#include "compat/sleep.hpp"
#include "compat/base-path.hpp"
+#include "compat/sleep.hpp"
+
#include <utility>
+#include <cstring>
#include <QTextStream>
#include <QTime>
-#include <QDebug>
+#include <QByteArray>
-#include <cstring>
+#include <QDebug>
void hatire_thread::sendcmd_impl(const QByteArray &cmd)
{
@@ -25,7 +27,7 @@ void hatire_thread::sendcmd_impl(const QByteArray &cmd)
Log(logMess);
com_port.write(cmd);
if (!com_port.waitForBytesWritten(1000)) {
- emit serial_debug_info_str(tr("Timeout during writing command"));
+ serial_debug_info_str(tr("Timeout during writing command"));
} else
{
Msg.append("\r\n");
@@ -43,7 +45,7 @@ void hatire_thread::sendcmd_impl(const QByteArray &cmd)
emit serial_debug_info(Msg);
#endif
} else {
- emit serial_debug_info_str(tr("COM port not open"));
+ serial_debug_info_str(tr("COM port not open"));
}
}
#endif
@@ -69,7 +71,7 @@ void hatire_thread::Log(const QString& message)
{
QTextStream out(&flDiagnostics);
QString milliSeconds;
- milliSeconds = QString("%1").arg(QTime::currentTime().msec(), 3, 10, QChar('0'));
+ milliSeconds = QStringLiteral("%1").arg(QTime::currentTime().msec(), 3, 10, QChar('0'));
// We have a file
out << QTime::currentTime().toString() << "." << milliSeconds << ": " << message << "\r\n";
flDiagnostics.close();
@@ -109,7 +111,7 @@ void hatire_thread::teardown_serial()
{
QByteArray msg;
Log("Tracker shut down");
- com_port.write(to_latin1(s.CmdStop));
+ com_port.write(s.CmdStop->toUtf8());
if (!com_port.waitForBytesWritten(1000))
{
emit serial_debug_info("TimeOut in writing CMD");
@@ -118,12 +120,13 @@ void hatire_thread::teardown_serial()
{
msg.append("\r\n");
msg.append("SEND '");
- msg.append(s.CmdStop);
+ msg.append(s.CmdStop->toUtf8());
msg.append("'\r\n");
}
emit serial_debug_info(msg);
- disconnect(&com_port, SIGNAL(readyRead()), nullptr, nullptr);
+ // XXX does this make any sense? -sh 20180703
+ //disconnect(&com_port, SIGNAL(readyRead()), nullptr, nullptr);
com_port.close();
}
}
@@ -146,7 +149,7 @@ void hatire_thread::run()
void hatire_thread::serial_debug_info_str(const QString& str)
{
- serial_debug_info(str.toUtf8());
+ emit serial_debug_info(str.toLatin1());
}
serial_result hatire_thread::init_serial_port_impl()
@@ -165,8 +168,8 @@ serial_result hatire_thread::init_serial_port_impl()
&& com_port.setParity((QSerialPort::Parity)s.pParity)
&& com_port.setStopBits((QSerialPort::StopBits)s.pStopBits)
&& com_port.setFlowControl((QSerialPort::FlowControl)s.pFlowControl)
+ && com_port.setDataTerminalReady(s.pDTR)
&& com_port.clear(QSerialPort::AllDirections)
- && com_port.setDataErrorPolicy(QSerialPort::IgnorePolicy)
)
{
Log(tr("Port Parameters set"));
@@ -198,7 +201,7 @@ serial_result hatire_thread::init_serial_port_impl()
}
Log(tr("Waiting on init"));
qDebug() << QTime::currentTime() << " HAT send INIT ";
- sendcmd_str(s.CmdInit);
+ emit sendcmd_str(s.CmdInit);
// Wait init MPU sequence
for (int i = 1; i <= s.DelayStart; i+=50)
{
@@ -206,7 +209,7 @@ serial_result hatire_thread::init_serial_port_impl()
}
// Send START cmd to IMU
qDebug() << QTime::currentTime() << " HAT send START ";
- sendcmd_str(s.CmdStart);
+ emit sendcmd_str(s.CmdStart);
// Wait start MPU sequence
for (int i = 1; i <=s.DelaySeq; i+=50)
@@ -239,13 +242,13 @@ void hatire_thread::serial_info_impl()
if (com_port.isOpen())
{
msg.append("\r\n");
- msg.append(com_port.portName());
+ msg.append(com_port.portName().toUtf8());
msg.append("\r\n");
msg.append("BAUDRATE :");
- msg.append(QString::number(com_port.baudRate()));
+ msg.append(QString::number(com_port.baudRate()).toLatin1());
msg.append("\r\n");
msg.append("DataBits :");
- msg.append(QString::number(com_port.dataBits()));
+ msg.append(QString::number(com_port.dataBits()).toLatin1());
msg.append("\r\n");
msg.append("Parity :");
@@ -301,13 +304,10 @@ void hatire_thread::serial_info_impl()
void hatire_thread::on_serial_read()
{
- const int sz = com_port.read(buf, sizeof(buf));
+ const int sz = (int)com_port.read(buf, sizeof(buf));
if (sz > 0)
{
- stat.input(timer.elapsed_ms());
- timer.start();
-
QMutexLocker lck(&data_mtx);
data_read.append(buf, sz);
}
@@ -319,20 +319,16 @@ void hatire_thread::on_serial_read()
}
#endif
- if (s.serial_bug_workaround || sz <= 0)
+ if (sz <= 0)
{
// qt can fire QSerialPort::readyRead() needlessly, causing a busy loop.
// see https://github.com/opentrack/opentrack/issues/327#issuecomment-207941003
+
+ // this probably happens with flaky BT/usb-to-serial converters (?)
constexpr int hz = 90;
constexpr int ms = 1000/hz;
portable::sleep(ms);
}
-
- if (throttle_timer.elapsed_ms() >= 3000)
- {
- throttle_timer.start();
- qDebug() << "stat:" << "avg" << stat.avg() << "stddev" << stat.stddev();
- }
}
QByteArray& hatire_thread::send_data_read_nolock()
diff --git a/tracker-hatire/thread.hpp b/tracker-hatire/thread.hpp
index 93c50c9e..f6bd8d49 100644
--- a/tracker-hatire/thread.hpp
+++ b/tracker-hatire/thread.hpp
@@ -4,16 +4,12 @@
#include "ftnoir_tracker_hat_settings.h"
#include <QSerialPort>
-#include <QByteArray>
#include <QThread>
#include <QMutex>
#include <QFile>
#include <QCoreApplication>
-#include "compat/variance.hpp"
-#include "compat/timer.hpp"
-
enum results
{
result_ok,
@@ -34,7 +30,7 @@ struct serial_result
serial_result(results code, const QString& error) : error(error), code(code) {}
QString error;
- results code;
+ results code { result_error };
};
class hatire_thread : public QThread
@@ -51,12 +47,9 @@ class hatire_thread : public QThread
QByteArray data_read;
serial_t com_port;
TrackerSettings s;
- variance stat;
- Timer timer, throttle_timer;
char buf[1024];
void run() override;
- static inline QByteArray to_latin1(const QString& str) { return str.toLatin1(); }
void serial_debug_info_str(const QString& str);
diff --git a/tracker-hydra/CMakeLists.txt b/tracker-hydra/CMakeLists.txt
index af44135f..48471765 100644
--- a/tracker-hydra/CMakeLists.txt
+++ b/tracker-hydra/CMakeLists.txt
@@ -1,5 +1,5 @@
set(SDK_HYDRA "" CACHE PATH "libSixense path for Razer Hydra")
-if(SDK_HYDRA)
+if(SDK_HYDRA AND opentrack-intel)
otr_module(tracker-hydra)
target_include_directories(opentrack-tracker-hydra SYSTEM PUBLIC ${SDK_HYDRA}/include ${SDK_HYDRA}/include/sixense_utils)
if(opentrack-64bit)
@@ -15,23 +15,29 @@ if(SDK_HYDRA)
set(dir bin)
set(ext dll)
endif()
- set(under_dll _dll)
- set(soext dll)
- target_link_libraries(opentrack-tracker-hydra "${SDK_HYDRA}/${dir}/win32/release_dll/sixense.${ext}")
- install(FILES "${SDK_HYDRA}/bin/win32/release_dll/sixense.dll" DESTINATION ${opentrack-hier-pfx} PERMISSIONS ${opentrack-perms-exec})
+ if (opentrack-64bit)
+ set(part "x64")
+ else()
+ set(part "win32")
+ endif()
+ set(lib "${SDK_HYDRA}/${dir}/${part}/release_dll/sixense${six4}.${ext}")
+ set(dll "${SDK_HYDRA}/bin/${part}/release_dll/sixense${six4}.dll")
+ install(FILES "${dll}" DESTINATION ${opentrack-libexec} PERMISSIONS ${opentrack-perms-exec})
+ target_link_libraries(opentrack-tracker-hydra "${lib}")
else()
if(APPLE)
- set(dest .)
- set(under-dll _dll)
- set(soext dylib)
- set(plat osx)
- else()
- set(dest ${opentrack-hier-pfx})
- set(under-dll)
- set(soext so)
- set(plat linux)
+ set(dest ".")
+ set(part "release_dll")
+ set(soext "dylib")
+ set(plat "osx")
+ else() # assume Linux
+ set(dest "${opentrack-libexec}")
+ set(part "release")
+ set(soext "so")
+ set(plat "linux")
endif()
- target_link_libraries(opentrack-tracker-hydra "${SDK_HYDRA}/lib/${plat}${six4}/release${under-dll}/libsixense${six4}.${soext}")
- install(FILES "${SDK_HYDRA}/lib/${plat}${six4}/release${under-dll}/libsixense${six4}.${soext}" DESTINATION ${opentrack-hier-pfx} PERMISSIONS ${opentrack-perms-exec})
+ set(lib "libsixense${six4}.${soext}")
+ target_link_libraries(opentrack-tracker-hydra "${SDK_HYDRA}/lib/${plat}${six4}/${part}/${lib}")
+ install(FILES "${SDK_HYDRA}/lib/${plat}${six4}/${part}/${lib}" DESTINATION ${opentrack-libexec} PERMISSIONS ${opentrack-perms-exec})
endif()
endif()
diff --git a/tracker-hydra/ftnoir_tracker_hydra.cpp b/tracker-hydra/ftnoir_tracker_hydra.cpp
index 69bb71e9..5f548bd6 100644
--- a/tracker-hydra/ftnoir_tracker_hydra.cpp
+++ b/tracker-hydra/ftnoir_tracker_hydra.cpp
@@ -18,12 +18,13 @@
#include <cstdio>
#include <cmath>
#ifdef _WIN32
+# undef WIN32
+# define WIN32
# define SIXENSE_STATIC_LIB
-# define SIXENSE_UTILS_STATIC_LIB
#endif
#include <sixense.h>
-Hydra_Tracker::Hydra_Tracker() {}
+Hydra_Tracker::Hydra_Tracker() = default;
#include <sixense_math.hpp>
diff --git a/tracker-hydra/ftnoir_tracker_hydra.h b/tracker-hydra/ftnoir_tracker_hydra.h
index 474a93f8..8af44344 100644
--- a/tracker-hydra/ftnoir_tracker_hydra.h
+++ b/tracker-hydra/ftnoir_tracker_hydra.h
@@ -5,7 +5,8 @@
#include "options/options.hpp"
using namespace options;
-struct settings : opts {
+struct settings : opts
+{
settings() :
opts("tracker-hydra")
{}
@@ -15,7 +16,7 @@ class Hydra_Tracker : public ITracker
{
public:
Hydra_Tracker();
- ~Hydra_Tracker();
+ ~Hydra_Tracker() override;
module_status start_tracker(QFrame *) override;
void data(double *data) override;
@@ -29,8 +30,8 @@ class dialog_hydra: public ITrackerDialog
Q_OBJECT
public:
dialog_hydra();
- void register_tracker(ITracker *) {}
- void unregister_tracker() {}
+ void register_tracker(ITracker *) override {}
+ void unregister_tracker() override {}
private:
settings s;
Ui::UIHydraControls ui;
@@ -41,8 +42,9 @@ private slots:
class hydraDll : public Metadata
{
-public:
- QString name() { return QString("Razer Hydra -- inertial device"); }
- QIcon icon() { return QIcon(":/images/opentrack.png"); }
+ Q_OBJECT
+
+ QString name() override { return QString("Razer Hydra -- inertial device"); }
+ QIcon icon() override { return QIcon(":/images/opentrack.png"); }
};
diff --git a/tracker-hydra/lang/zh_CN.ts b/tracker-hydra/lang/zh_CN.ts
index f5440284..f1257856 100644
--- a/tracker-hydra/lang/zh_CN.ts
+++ b/tracker-hydra/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>UIHydraControls</name>
<message>
diff --git a/tracker-joystick/ftnoir_tracker_joystick.cpp b/tracker-joystick/ftnoir_tracker_joystick.cpp
index 27a5408d..940abfd1 100644
--- a/tracker-joystick/ftnoir_tracker_joystick.cpp
+++ b/tracker-joystick/ftnoir_tracker_joystick.cpp
@@ -11,10 +11,10 @@
joystick::joystick()
{
- if (static_cast<QString>(s.guid) == "")
+ if (s.guid->isEmpty())
{
std::vector<win32_joy_ctx::joy_info> info = joy_ctx.get_joy_info();
- if (info.size())
+ if (!info.empty())
{
s.guid = info[0].guid;
s.b->save();
@@ -22,9 +22,7 @@ joystick::joystick()
}
}
-joystick::~joystick()
-{
-}
+joystick::~joystick() = default;
void joystick::data(double *data)
{
@@ -58,8 +56,8 @@ void joystick::data(double *data)
if (k < 0 || k >= 8)
data[i] = 0;
else
- data[i] = clamp(axes[k] * limits[i] / AXIS_MAX,
- -limits[i], limits[i]);
+ data[i] = std::clamp(axes[k] * limits[i] / AXIS_MAX,
+ -limits[i], limits[i]);
}
}
}
diff --git a/tracker-joystick/ftnoir_tracker_joystick.h b/tracker-joystick/ftnoir_tracker_joystick.h
index f84a13d2..d59c70de 100644
--- a/tracker-joystick/ftnoir_tracker_joystick.h
+++ b/tracker-joystick/ftnoir_tracker_joystick.h
@@ -45,7 +45,7 @@ public:
void data(double *data);
settings s;
QString guid;
- static constexpr inline int AXIS_MAX = win32_joy_ctx::joy_axis_size;
+ static constexpr int AXIS_MAX = win32_joy_ctx::joy_axis_size;
win32_joy_ctx joy_ctx;
};
@@ -63,7 +63,7 @@ public:
QString name;
QString guid;
};
- QList<joys> _joys;
+ QList<joys> joys_;
private slots:
void doOK();
void doCancel();
@@ -71,8 +71,9 @@ private slots:
class joystickDll : public Metadata
{
-public:
- QString name() { return otr_tr("Joystick input"); }
+ Q_OBJECT
+
+ QString name() { return tr("Joystick input"); }
QIcon icon() { return QIcon(":/images/opentrack.png"); }
};
diff --git a/tracker-joystick/ftnoir_tracker_joystick_dialog.cpp b/tracker-joystick/ftnoir_tracker_joystick_dialog.cpp
index aa4b783a..aaf3037b 100644
--- a/tracker-joystick/ftnoir_tracker_joystick_dialog.cpp
+++ b/tracker-joystick/ftnoir_tracker_joystick_dialog.cpp
@@ -12,18 +12,18 @@ dialog_joystick::dialog_joystick() : tracker(nullptr)
{
win32_joy_ctx joy_ctx;
- _joys = QList<joys>();
+ joys_ = {};
- for (auto j : joy_ctx.get_joy_info())
- _joys.push_back(joys { j.name, j.guid });
+ for (const auto& j : joy_ctx.get_joy_info())
+ joys_.push_back(joys { j.name, j.guid });
}
{
const QString guid = s.guid;
int idx = 0;
- for (int i = 0; i < _joys.size(); i++)
+ for (int i = 0; i < joys_.size(); i++)
{
- const joys& j = _joys[i];
+ const joys& j = joys_[i];
if (j.guid == guid)
idx = i;
ui.joylist->addItem(j.name + " " + j.guid);
@@ -41,8 +41,8 @@ dialog_joystick::dialog_joystick() : tracker(nullptr)
void dialog_joystick::doOK() {
int idx = ui.joylist->currentIndex();
- joys def { "", "" };
- auto val = _joys.value(idx, def);
+ static const joys def { {}, {} };
+ auto val = joys_.value(idx, def);
s.guid = val.guid;
s.b->save();
close();
diff --git a/tracker-joystick/lang/nl_NL.ts b/tracker-joystick/lang/nl_NL.ts
index 0b10b88e..691b7307 100644
--- a/tracker-joystick/lang/nl_NL.ts
+++ b/tracker-joystick/lang/nl_NL.ts
@@ -76,4 +76,11 @@
<translation>Rol</translation>
</message>
</context>
+<context>
+ <name>joystickDll</name>
+ <message>
+ <source>Joystick input</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-joystick/lang/ru_RU.ts b/tracker-joystick/lang/ru_RU.ts
index 3b1b980e..e737569e 100644
--- a/tracker-joystick/lang/ru_RU.ts
+++ b/tracker-joystick/lang/ru_RU.ts
@@ -76,4 +76,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>joystickDll</name>
+ <message>
+ <source>Joystick input</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-joystick/lang/stub.ts b/tracker-joystick/lang/stub.ts
index 9a6e7747..a169a05c 100644
--- a/tracker-joystick/lang/stub.ts
+++ b/tracker-joystick/lang/stub.ts
@@ -76,4 +76,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>joystickDll</name>
+ <message>
+ <source>Joystick input</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-joystick/lang/zh_CN.ts b/tracker-joystick/lang/zh_CN.ts
index 9a6e7747..b4729a64 100644
--- a/tracker-joystick/lang/zh_CN.ts
+++ b/tracker-joystick/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>UIJoystickControls</name>
<message>
@@ -76,4 +76,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>joystickDll</name>
+ <message>
+ <source>Joystick input</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-kinect-face/CMakeLists.txt b/tracker-kinect-face/CMakeLists.txt
new file mode 100644
index 00000000..d205a764
--- /dev/null
+++ b/tracker-kinect-face/CMakeLists.txt
@@ -0,0 +1,55 @@
+# Kinect SDK is Windows only
+if (WIN32 AND opentrack-intel)
+ # Setup cache variable to Kinect SDK path
+ set(SDK_KINECT20 "$ENV{KINECTSDK20_DIR}" CACHE PATH "Kinect SDK path")
+ # If we have a valid SDK path, try build that tracker
+ if(SDK_KINECT20)
+ # Register our module
+ otr_module(tracker-kinect-face)
+
+ if(MSVC)
+ # workaround warning in SDK
+ target_compile_options(${self} PRIVATE "-wd4471")
+ endif()
+
+ # Add include path to Kinect SDK
+ target_include_directories(${self} SYSTEM PRIVATE "${SDK_KINECT20}/inc")
+
+ # Check processor architecture
+ if(opentrack-64bit)
+ # 64 bits
+ set(kinect-arch-dir "x64")
+ else()
+ # 32 bits
+ set(kinect-arch-dir "x86")
+ endif()
+
+ # Link against Kinect SDK libraries
+ target_link_libraries(${self} "${SDK_KINECT20}/lib/${kinect-arch-dir}/Kinect20.lib" "${SDK_KINECT20}/lib/${kinect-arch-dir}/Kinect20.Face.lib")
+ # Link against video utilities, needed for video preview
+ target_link_libraries(${self} opentrack-video)
+
+ # Install Kinect Face DLL
+ install(FILES "${SDK_KINECT20}/Redist/Face/${kinect-arch-dir}/Kinect20.Face.dll" DESTINATION "${opentrack-libexec}" PERMISSIONS ${opentrack-perms-exec})
+ # Install Kinect Face Database
+ install(DIRECTORY "${SDK_KINECT20}/Redist/Face/${kinect-arch-dir}/NuiDatabase" DESTINATION "${opentrack-libexec}")
+
+ set(redist-dir "${CMAKE_SOURCE_DIR}/redist/${kinect-arch-dir}")
+ #install(
+ # FILES "${redist-dir}/msvcp110.dll" "${redist-dir}/msvcr110.dll"
+ # DESTINATION "${opentrack-libexec}"
+ # PERMISSIONS ${opentrack-perms-exec}
+ #)
+
+ # Optional OpenCV support
+ # Needed for Point Tracker to support Kinect V2 IR Sensor
+ include(opentrack-opencv)
+ find_package(OpenCV QUIET)
+
+ if(OpenCV_FOUND)
+ add_definitions(-DOTR_HAVE_OPENCV)
+ target_include_directories(${self} SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS})
+ target_link_libraries(${self} opencv_imgproc opentrack-cv opencv_core opentrack-video)
+ endif()
+ endif()
+endif()
diff --git a/tracker-kinect-face/camera_kinect_ir.cpp b/tracker-kinect-face/camera_kinect_ir.cpp
new file mode 100644
index 00000000..3a33fd14
--- /dev/null
+++ b/tracker-kinect-face/camera_kinect_ir.cpp
@@ -0,0 +1,301 @@
+/* Copyright (c) 2019, Stephane Lenclud <github@lenclud.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 "camera_kinect_ir.h"
+
+#ifdef OTR_HAVE_OPENCV
+
+ //#include "frame.hpp"
+
+#include "compat/sleep.hpp"
+#include "compat/math-imports.hpp"
+#include "compat/camera-names.hpp"
+
+#include <opencv2/imgproc.hpp>
+#include <cstdlib>
+
+namespace Kinect {
+
+ static const char KKinectIRSensor[] = "Kinect V2 IR Sensor";
+
+ // Safe release for interfaces
+ template<class Interface>
+ inline void SafeRelease(Interface *& pInterfaceToRelease)
+ {
+ if (pInterfaceToRelease != NULL)
+ {
+ pInterfaceToRelease->Release();
+ pInterfaceToRelease = NULL;
+ }
+ }
+
+ CamerasProvider::CamerasProvider() = default;
+
+ std::unique_ptr<video::impl::camera> CamerasProvider::make_camera(const QString& name)
+ {
+ if (name.compare(KKinectIRSensor) == 0)
+ {
+ return std::make_unique<InfraredCamera>();
+ }
+
+ return nullptr;
+ }
+
+ std::vector<QString> CamerasProvider::camera_names() const
+ {
+ auto list = get_camera_names();
+ auto it = std::find_if(list.cbegin(), list.cend(), [](const auto& x) {
+ const auto& [name, idx] = x;
+ return name.startsWith("Kinect V2 Video Sensor [");
+ });
+ if (it != list.cend())
+ {
+ // We found Kinect V2 Video Sensor therefore we have a kinect V2 connected.
+ // Publish our Kinect V2 IR Sensor implementation then.
+ return { KKinectIRSensor };
+ }
+ else
+ {
+ return {};
+ }
+ }
+
+ bool CamerasProvider::can_show_dialog(const QString& camera_name)
+ {
+ return false;
+ }
+
+ bool CamerasProvider::show_dialog(const QString& camera_name)
+ {
+ return false;
+ }
+
+ // Register our camera provider thus making sure Point Tracker can use Kinect V2 IR Sensor
+ OTR_REGISTER_CAMERA(CamerasProvider)
+
+
+ InfraredCamera::InfraredCamera()
+ {
+ }
+
+
+ InfraredCamera::~InfraredCamera()
+ {
+ stop();
+ }
+
+ bool InfraredCamera::show_dialog()
+ {
+ return false;
+ }
+
+ bool InfraredCamera::is_open()
+ {
+ return iInfraredFrameReader != nullptr;
+ }
+
+ ///
+ /// Wait until we get a first frame
+ ///
+ void InfraredCamera::WaitForFirstFrame()
+ {
+ bool new_frame = false;
+ int attempts = 200; // Kinect cold start can take a while
+ while (!new_frame && attempts > 0)
+ {
+ new_frame = get_frame_(iMatFrame);
+ portable::sleep(100);
+ --attempts;
+ }
+ }
+
+
+
+ std::tuple<const video::impl::frame&, bool> InfraredCamera::get_frame()
+ {
+ bool new_frame = false;
+ new_frame = get_frame_(iMatFrame);
+
+ iFrame.data = iMatFrame.ptr();
+ iFrame.width = iWidth;
+ iFrame.height = iHeight;
+ iFrame.stride = cv::Mat::AUTO_STEP;
+ iFrame.channels = iMatFrame.channels();
+ iFrame.channel_size = iMatFrame.elemSize1();
+ return { iFrame, new_frame };
+ }
+
+ ///
+ ///
+ ///
+ bool InfraredCamera::start(info& aInfo)
+ {
+ stop();
+
+ HRESULT hr;
+
+ // Get and open Kinect sensor
+ hr = GetDefaultKinectSensor(&iKinectSensor);
+ if (SUCCEEDED(hr))
+ {
+ hr = iKinectSensor->Open();
+ }
+
+ // Create infrared frame reader
+ if (SUCCEEDED(hr))
+ {
+ // Initialize the Kinect and get the infrared reader
+ IInfraredFrameSource* pInfraredFrameSource = NULL;
+
+ hr = iKinectSensor->Open();
+
+ if (SUCCEEDED(hr))
+ {
+ hr = iKinectSensor->get_InfraredFrameSource(&pInfraredFrameSource);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pInfraredFrameSource->OpenReader(&iInfraredFrameReader);
+ }
+
+ SafeRelease(pInfraredFrameSource);
+
+ if (SUCCEEDED(hr))
+ {
+ iKinectSensor->get_CoordinateMapper(&iCoordinateMapper);
+ }
+ }
+
+
+ if (SUCCEEDED(hr))
+ {
+ WaitForFirstFrame();
+ bool success = iMatFrame.ptr() != nullptr;
+ if (success)
+ {
+ // Provide frame info
+ aInfo.width = iWidth;
+ aInfo.height = iHeight;
+
+ CameraIntrinsics intrinsics;
+ hr = iCoordinateMapper->GetDepthCameraIntrinsics(&intrinsics);
+ if (SUCCEEDED(hr))
+ {
+ aInfo.fx = intrinsics.FocalLengthX;
+ aInfo.fy = intrinsics.FocalLengthY;
+ aInfo.P_x = intrinsics.PrincipalPointX;
+ aInfo.P_y = intrinsics.PrincipalPointY;
+ aInfo.dist_c[1] = intrinsics.RadialDistortionSecondOrder;
+ aInfo.dist_c[3] = intrinsics.RadialDistortionFourthOrder;
+ aInfo.dist_c[5] = intrinsics.RadialDistortionSixthOrder;
+ }
+
+ }
+
+ return success;
+ }
+
+ stop();
+ return false;
+ }
+
+ void InfraredCamera::stop()
+ {
+ // done with infrared frame reader
+ SafeRelease(iInfraredFrame);
+ SafeRelease(iInfraredFrameReader);
+
+ // close the Kinect Sensor
+ if (iKinectSensor)
+ {
+ iKinectSensor->Close();
+ }
+
+ SafeRelease(iCoordinateMapper);
+ SafeRelease(iKinectSensor);
+
+ // Free up our memory buffer if any
+ iMatFrame = cv::Mat();
+ }
+
+ bool InfraredCamera::get_frame_(cv::Mat& aFrame)
+ {
+
+ if (!iInfraredFrameReader)
+ {
+ return false;
+ }
+
+ bool success = false;
+
+ // Release previous frame if any
+ SafeRelease(iInfraredFrame);
+
+ Sleep(34); // FIXME
+ HRESULT hr = iInfraredFrameReader->AcquireLatestFrame(&iInfraredFrame);
+
+ if (SUCCEEDED(hr))
+ {
+ if (iFirstFrame)
+ {
+ IFrameDescription* frameDescription = NULL;
+
+ if (SUCCEEDED(hr))
+ {
+ hr = iInfraredFrame->get_FrameDescription(&frameDescription);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = frameDescription->get_Width(&iWidth);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = frameDescription->get_Height(&iHeight);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = frameDescription->get_DiagonalFieldOfView(&iFov);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ iFirstFrame = false;
+ }
+
+ SafeRelease(frameDescription);
+ }
+
+
+ UINT nBufferSize = 0;
+ UINT16 *pBuffer = NULL;
+
+ if (SUCCEEDED(hr))
+ {
+ hr = iInfraredFrame->AccessUnderlyingBuffer(&nBufferSize, &pBuffer);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ // Create an OpenCV matrix with our 16-bits IR buffer
+ aFrame = cv::Mat(iHeight, iWidth, CV_16UC1, pBuffer, cv::Mat::AUTO_STEP);
+ // Any processing of the frame is left to the user
+ success = true;
+ }
+ }
+
+
+ return success;
+ }
+
+}
+
+#endif
diff --git a/tracker-kinect-face/camera_kinect_ir.h b/tracker-kinect-face/camera_kinect_ir.h
new file mode 100644
index 00000000..83acb4d6
--- /dev/null
+++ b/tracker-kinect-face/camera_kinect_ir.h
@@ -0,0 +1,82 @@
+/* Copyright (c) 2019, Stephane Lenclud <github@lenclud.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
+
+#ifdef OTR_HAVE_OPENCV
+
+#include <Kinect.h>
+
+//#include "pt-api.hpp"
+#include "compat/timer.hpp"
+#include "video/camera.hpp"
+
+
+#include <memory>
+
+#include <opencv2/core.hpp>
+#include <opencv2/videoio.hpp>
+
+#include <QString>
+
+namespace Kinect {
+
+ struct CamerasProvider : video::impl::camera_
+ {
+ CamerasProvider();
+ std::vector<QString> camera_names() const override;
+ std::unique_ptr<video::impl::camera> make_camera(const QString& name) override;
+ bool can_show_dialog(const QString& camera_name) override;
+ bool show_dialog(const QString& camera_name) override;
+ };
+
+
+///
+/// Implement our camera interface using Kinect V2 SDK IR Sensor.
+///
+struct InfraredCamera final : video::impl::camera
+{
+ InfraredCamera();
+ ~InfraredCamera() override;
+
+ // From video::impl::camera
+ [[nodiscard]] bool start(info& args) override;
+ void stop() override;
+ bool is_open() override;
+ std::tuple<const video::impl::frame&, bool> get_frame() override;
+ [[nodiscard]] bool show_dialog() override;
+
+private:
+ bool get_frame_(cv::Mat& frame);
+ void WaitForFirstFrame();
+
+private:
+ // Current Kinect
+ IKinectSensor* iKinectSensor = nullptr;
+
+ // Infrared reader
+ IInfraredFrameReader* iInfraredFrameReader = nullptr;
+
+ // Frame needs to stay alive while we access the data buffer
+ IInfraredFrame* iInfraredFrame = nullptr;
+
+ //
+ ICoordinateMapper* iCoordinateMapper = nullptr;
+
+ video::frame iFrame;
+ cv::Mat iMatFrame;
+
+ float iFov = 0;
+ int iWidth = 0, iHeight = 0;
+ bool iFirstFrame = true;
+};
+
+}
+
+
+#endif
diff --git a/tracker-kinect-face/images/kinect.png b/tracker-kinect-face/images/kinect.png
new file mode 100644
index 00000000..fd8f5f77
--- /dev/null
+++ b/tracker-kinect-face/images/kinect.png
Binary files differ
diff --git a/tracker-tobii-eyex/tobii-eyex-res.qrc b/tracker-kinect-face/kinect_face.qrc
index e3395df9..8b27c81e 100644
--- a/tracker-tobii-eyex/tobii-eyex-res.qrc
+++ b/tracker-kinect-face/kinect_face.qrc
@@ -1,5 +1,5 @@
<RCC>
<qresource prefix="/">
- <file>images/tobii-eyex-logo.png</file>
+ <file>images/kinect.png</file>
</qresource>
</RCC>
diff --git a/tracker-kinect-face/kinect_face_settings.cpp b/tracker-kinect-face/kinect_face_settings.cpp
new file mode 100644
index 00000000..d8012aa3
--- /dev/null
+++ b/tracker-kinect-face/kinect_face_settings.cpp
@@ -0,0 +1,38 @@
+/* Copyright (c) 2019, Stephane Lenclud <github@lenclud.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 "kinect_face_settings.h"
+#include "kinect_face_tracker.h"
+#include "api/plugin-api.hpp"
+#include "compat/math-imports.hpp"
+#include "compat/library-path.hpp"
+
+#include <cmath>
+
+#include <QDesktopServices>
+#include <QUrl>
+#include <QPushButton>
+#include <QDebug>
+
+KinectFaceSettings::KinectFaceSettings()
+{
+ ui.setupUi(this);
+
+ connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &KinectFaceSettings::close);
+ connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &KinectFaceSettings::close);
+
+ static const QUrl path {"file:///" + application_base_path() + OPENTRACK_DOC_PATH "/3rdparty-notices/Kinect-V2-SDK-Eula.rtf" };
+
+ connect(ui.buttonBox, &QDialogButtonBox::helpRequested, [] {
+ QDesktopServices::openUrl(path);
+ });
+
+ ui.buttonBox->addButton(tr("Kinect license"), QDialogButtonBox::HelpRole);
+}
+
+OPENTRACK_DECLARE_TRACKER(KinectFaceTracker, KinectFaceSettings, KinectFaceMetadata)
diff --git a/tracker-kinect-face/kinect_face_settings.h b/tracker-kinect-face/kinect_face_settings.h
new file mode 100644
index 00000000..2c5cc55f
--- /dev/null
+++ b/tracker-kinect-face/kinect_face_settings.h
@@ -0,0 +1,32 @@
+/* Copyright (c) 2019, Stephane Lenclud <github@lenclud.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 "ui_kinect_face_settings.h"
+#include "api/plugin-api.hpp"
+
+class KinectFaceSettings : public ITrackerDialog
+{
+ Q_OBJECT
+
+ Ui::KinectFaceUi ui;
+
+public:
+ KinectFaceSettings();
+ void register_tracker(ITracker *) override {}
+ void unregister_tracker() override {}
+};
+
+class KinectFaceMetadata : public Metadata
+{
+ Q_OBJECT
+
+ QString name() override { return tr("Kinect Face 0.1"); }
+ QIcon icon() override { return QIcon(":/images/kinect.png"); }
+};
+
diff --git a/tracker-kinect-face/kinect_face_settings.ui b/tracker-kinect-face/kinect_face_settings.ui
new file mode 100644
index 00000000..9e4b2b41
--- /dev/null
+++ b/tracker-kinect-face/kinect_face_settings.ui
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>KinectFaceUi</class>
+ <widget class="QWidget" name="KinectFaceUi">
+ <property name="windowModality">
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>386</width>
+ <height>153</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>386</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Kinect Face Tracker</string>
+ </property>
+ <property name="windowIcon">
+ <iconset>
+ <normaloff>:/images/kinect.png</normaloff>:/images/kinect.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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;justify&quot;&gt;Start OpenTrack to check if Kinect Face Tracker is working.&lt;/p&gt;&lt;p align=&quot;justify&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note&lt;/span&gt;: When using OpenTrack with a Kinect for Windows v2 Sensor, Microsoft will collect telemetry data (e.g. operating system, number of processors, graphic chipset, memory, device type, locale, time) in order to improve Microsoft products and services. The data will not be used to identify specific individuals.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </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/>
+ <connections/>
+ <slots>
+ <slot>startEngineClicked()</slot>
+ <slot>stopEngineClicked()</slot>
+ <slot>cameraSettingsClicked()</slot>
+ </slots>
+</ui>
diff --git a/tracker-kinect-face/kinect_face_tracker.cpp b/tracker-kinect-face/kinect_face_tracker.cpp
new file mode 100644
index 00000000..4bab2eef
--- /dev/null
+++ b/tracker-kinect-face/kinect_face_tracker.cpp
@@ -0,0 +1,614 @@
+/* Copyright (c) 2019, Stéphane Lenclud <github@lenclud.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 "kinect_face_tracker.h"
+
+#include <QLayout>
+#include <QPainter>
+
+#include "compat/check-visible.hpp"
+
+static const int KColorWidth = 1920;
+static const int KColorHeight = 1080;
+
+///
+bool IsValidRect(const RectI& aRect)
+{
+ if (aRect.Bottom != 0)
+ {
+ return true;
+ }
+
+ if (aRect.Left != 0)
+ {
+ return true;
+ }
+
+ if (aRect.Right != 0)
+ {
+ return true;
+ }
+
+ if (aRect.Top != 0)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+///
+bool IsNullVetor(const Vector4& aVector)
+{
+ if (aVector.w != 0)
+ {
+ return false;
+ }
+
+ if (aVector.x != 0)
+ {
+ return false;
+ }
+
+ if (aVector.y != 0)
+ {
+ return false;
+ }
+
+ if (aVector.z != 0)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+///
+bool IsNullPoint(const CameraSpacePoint& aPoint)
+{
+ if (aPoint.X != 0)
+ {
+ return false;
+ }
+
+ if (aPoint.Y != 0)
+ {
+ return false;
+ }
+
+ if (aPoint.Z != 0)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+KinectFaceTracker::KinectFaceTracker()
+{
+ // create heap storage for color pixel data in RGBX format
+ iColorRGBX = new RGBQUAD[KColorWidth * KColorHeight];
+}
+
+KinectFaceTracker::~KinectFaceTracker()
+{
+ if (iColorRGBX)
+ {
+ delete[] iColorRGBX;
+ iColorRGBX = nullptr;
+ }
+
+ // clean up Direct2D
+ //SafeRelease(m_pD2DFactory);
+
+ // done with face sources and readers
+ SafeRelease(iFaceFrameSource);
+ SafeRelease(iFaceFrameReader);
+
+ // done with body frame reader
+ SafeRelease(iBodyFrameReader);
+
+ // done with color frame reader
+ SafeRelease(iColorFrameReader);
+
+ // close the Kinect Sensor
+ if (iKinectSensor)
+ {
+ iKinectSensor->Close();
+ }
+
+ SafeRelease(iKinectSensor);
+}
+
+module_status KinectFaceTracker::start_tracker(QFrame* aFrame)
+{
+ iTimer.start();
+
+ if (SUCCEEDED(InitializeDefaultSensor()))
+ {
+ // Setup our video preview widget
+ iVideoWidget = std::make_unique<video_widget>(aFrame);
+ iLayout = std::make_unique<QHBoxLayout>(aFrame);
+ iLayout->setContentsMargins(0, 0, 0, 0);
+ iLayout->addWidget(&*iVideoWidget);
+ aFrame->setLayout(&*iLayout);
+ //video_widget->resize(video_frame->width(), video_frame->height());
+ aFrame->show();
+
+ return status_ok();
+ }
+
+ return error("Kinect init failed!");
+}
+
+
+bool KinectFaceTracker::center()
+{
+ // Mark our center
+ iFacePositionCenter = iFacePosition;
+ iFaceRotationCenter = iFaceRotation;
+ return true;
+}
+
+//
+//
+//
+void KinectFaceTracker::data(double *data)
+{
+ const double dt = iTimer.elapsed_seconds();
+
+ const double KMinDelayInSeconds = 1.0 / 30.0; // Pointless running faster than Kinect hardware itself
+ if (dt > KMinDelayInSeconds)
+ {
+ iTimer.start(); // Reset our timer
+ //OutputDebugStringA("Updating frame!\n");
+ Update();
+ ExtractFaceRotationInDegrees(&iFaceRotationQuaternion, &iFaceRotation.X, &iFaceRotation.Y, &iFaceRotation.Z);
+ //Check if data is valid
+ if (IsValidRect(iFaceBox))
+ {
+ // We have valid tracking retain position and rotation
+ iLastFacePosition = iFacePosition;
+ iLastFaceRotation = iFaceRotation;
+ }
+ else
+ {
+ //TODO: after like 5s without tracking reset position to zero
+ //TODO: Instead of hardcoding that delay add it to our settings
+ }
+ }
+ else
+ {
+ //OutputDebugStringA("Skipping frame!\n");
+ }
+
+ // Feed our framework our last valid position and rotation
+ data[0] = (iLastFacePosition.X - iFacePositionCenter.X) * 100; // Convert to centimer to be in a range that suites OpenTrack.
+ data[1] = (iLastFacePosition.Y - iFacePositionCenter.Y) * 100;
+ data[2] = (iLastFacePosition.Z - iFacePositionCenter.Z) * 100;
+
+ // Yaw, Picth, Roll
+ data[3] = -(iLastFaceRotation.X - iFaceRotationCenter.X); // Invert to be compatible with ED out-of-the-box
+ data[4] = (iLastFaceRotation.Y - iFaceRotationCenter.Y);
+ data[5] = (iLastFaceRotation.Z - iFaceRotationCenter.Z);
+}
+
+
+/// <summary>
+/// Converts rotation quaternion to Euler angles
+/// And then maps them to a specified range of values to control the refresh rate
+/// </summary>
+/// <param name="pQuaternion">face rotation quaternion</param>
+/// <param name="pPitch">rotation about the X-axis</param>
+/// <param name="pYaw">rotation about the Y-axis</param>
+/// <param name="pRoll">rotation about the Z-axis</param>
+void KinectFaceTracker::ExtractFaceRotationInDegrees(const Vector4* pQuaternion, float* pYaw, float* pPitch, float* pRoll)
+{
+ double x = pQuaternion->x;
+ double y = pQuaternion->y;
+ double z = pQuaternion->z;
+ double w = pQuaternion->w;
+
+ // convert face rotation quaternion to Euler angles in degrees
+ double dPitch, dYaw, dRoll;
+ dPitch = atan2(2 * (y * z + w * x), w * w - x * x - y * y + z * z) / M_PI * 180.0;
+ dYaw = asin(2 * (w * y - x * z)) / M_PI * 180.0;
+ dRoll = atan2(2 * (x * y + w * z), w * w + x * x - y * y - z * z) / M_PI * 180.0;
+
+ // clamp rotation values in degrees to a specified range of values to control the refresh rate
+ /*
+ double increment = c_FaceRotationIncrementInDegrees;
+ *pPitch = static_cast<int>(floor((dPitch + increment/2.0 * (dPitch > 0 ? 1.0 : -1.0)) / increment) * increment);
+ *pYaw = static_cast<int>(floor((dYaw + increment/2.0 * (dYaw > 0 ? 1.0 : -1.0)) / increment) * increment);
+ *pRoll = static_cast<int>(floor((dRoll + increment/2.0 * (dRoll > 0 ? 1.0 : -1.0)) / increment) * increment);
+ */
+
+ *pPitch = dPitch;
+ *pYaw = dYaw;
+ *pRoll = dRoll;
+}
+
+
+
+/// <summary>
+/// Initializes the default Kinect sensor
+/// </summary>
+/// <returns>S_OK on success else the failure code</returns>
+HRESULT KinectFaceTracker::InitializeDefaultSensor()
+{
+ HRESULT hr;
+
+ // Get and open Kinect sensor
+ hr = GetDefaultKinectSensor(&iKinectSensor);
+ if (SUCCEEDED(hr))
+ {
+ hr = iKinectSensor->Open();
+ }
+
+ // Create color frame reader
+ if (SUCCEEDED(hr))
+ {
+ UniqueInterface<IColorFrameSource> colorFrameSource;
+ hr = iKinectSensor->get_ColorFrameSource(colorFrameSource.PtrPtr());
+ colorFrameSource.Reset();
+
+ if (SUCCEEDED(hr))
+ {
+ hr = colorFrameSource->OpenReader(&iColorFrameReader);
+ }
+ }
+
+ // Create body frame reader
+ if (SUCCEEDED(hr))
+ {
+ UniqueInterface<IBodyFrameSource> bodyFrameSource;
+ hr = iKinectSensor->get_BodyFrameSource(bodyFrameSource.PtrPtr());
+ bodyFrameSource.Reset();
+
+ if (SUCCEEDED(hr))
+ {
+ hr = bodyFrameSource->OpenReader(&iBodyFrameReader);
+ }
+ }
+
+ // Create HD face frame source
+ if (SUCCEEDED(hr))
+ {
+ // create the face frame source by specifying the required face frame features
+ hr = CreateHighDefinitionFaceFrameSource(iKinectSensor, &iFaceFrameSource);
+ }
+
+ // Create HD face frame reader
+ if (SUCCEEDED(hr))
+ {
+ // open the corresponding reader
+ hr = iFaceFrameSource->OpenReader(&iFaceFrameReader);
+ }
+
+ return hr;
+}
+
+
+
+/// <summary>
+/// Main processing function
+/// </summary>
+void KinectFaceTracker::Update()
+{
+ if (!iColorFrameReader || !iBodyFrameReader)
+ {
+ return;
+ }
+
+ IColorFrame* pColorFrame = nullptr;
+ HRESULT hr = iColorFrameReader->AcquireLatestFrame(&pColorFrame);
+
+ if (SUCCEEDED(hr))
+ {
+ INT64 nTime = 0;
+ IFrameDescription* pFrameDescription = nullptr;
+ int nWidth = 0;
+ int nHeight = 0;
+ ColorImageFormat imageFormat = ColorImageFormat_None;
+ UINT nBufferSize = 0;
+ RGBQUAD *pBuffer = nullptr;
+
+ hr = pColorFrame->get_RelativeTime(&nTime);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pColorFrame->get_FrameDescription(&pFrameDescription);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pFrameDescription->get_Width(&nWidth);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pFrameDescription->get_Height(&nHeight);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pColorFrame->get_RawColorImageFormat(&imageFormat);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ //DrawStreams(nTime, pBuffer, nWidth, nHeight);
+ ProcessFaces();
+ }
+
+ if (check_is_visible())
+ {
+ //OutputDebugStringA("Widget visible!\n");
+ // If our widget is visible we feed it our frame
+ if (SUCCEEDED(hr))
+ {
+ // Fetch color buffer
+ if (imageFormat == ColorImageFormat_Rgba)
+ {
+ hr = pColorFrame->AccessRawUnderlyingBuffer(&nBufferSize, reinterpret_cast<BYTE**>(&pBuffer));
+ }
+ else if (iColorRGBX)
+ {
+ pBuffer = iColorRGBX;
+ nBufferSize = KColorWidth * KColorHeight * sizeof(RGBQUAD);
+ hr = pColorFrame->CopyConvertedFrameDataToArray(nBufferSize, reinterpret_cast<BYTE*>(pBuffer), ColorImageFormat_Rgba);
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ // Setup our image
+ QImage image((const unsigned char*)pBuffer, KColorWidth, KColorHeight, sizeof(RGBQUAD)*KColorWidth, QImage::Format_RGBA8888);
+ if (IsValidRect(iFaceBox))
+ {
+ // Draw our face bounding box
+ QPainter painter(&image);
+ painter.setBrush(Qt::NoBrush);
+ painter.setPen(QPen(Qt::red, 8));
+ painter.drawRect(iFaceBox.Left, iFaceBox.Top, iFaceBox.Right - iFaceBox.Left, iFaceBox.Bottom - iFaceBox.Top);
+ bool bEnd = painter.end();
+ (void)bEnd;
+ }
+
+ // Update our video preview
+ iVideoWidget->update_image(image);
+ }
+
+ }
+
+
+ SafeRelease(pFrameDescription);
+ }
+
+ SafeRelease(pColorFrame);
+}
+
+
+/// <summary>
+/// Updates body data
+/// </summary>
+/// <param name="ppBodies">pointer to the body data storage</param>
+/// <returns>indicates success or failure</returns>
+HRESULT KinectFaceTracker::UpdateBodyData(IBody** ppBodies)
+{
+ HRESULT hr = E_FAIL;
+
+ if (iBodyFrameReader != nullptr)
+ {
+ IBodyFrame* pBodyFrame = nullptr;
+ hr = iBodyFrameReader->AcquireLatestFrame(&pBodyFrame);
+ if (SUCCEEDED(hr))
+ {
+ hr = pBodyFrame->GetAndRefreshBodyData(BODY_COUNT, ppBodies);
+ }
+ SafeRelease(pBodyFrame);
+ }
+
+ return hr;
+}
+
+
+float VectorLengthSquared(CameraSpacePoint point)
+{
+ float lenghtSquared = pow(point.X, 2) + pow(point.Y, 2) + pow(point.Z, 2);
+
+ //result = Math.Sqrt(result);
+ return lenghtSquared;
+}
+
+//
+// Finds the closest body from the sensor if any
+//
+IBody* KinectFaceTracker::FindClosestBody(IBody** aBodies)
+{
+ IBody* result = nullptr;
+ float closestBodyDistance = std::numeric_limits<float>::max();
+
+ for(int i=0;i<BODY_COUNT;i++)
+ {
+ BOOLEAN tracked;
+ aBodies[i]->get_IsTracked(&tracked);
+
+ if (tracked)
+ {
+ Joint joints[JointType_Count];
+ HRESULT hr = aBodies[i]->GetJoints(JointType_Count,joints);
+ if (FAILED(hr))
+ {
+ continue;
+ }
+
+ auto currentLocation = joints[JointType_SpineBase].Position;
+ auto currentDistance = VectorLengthSquared(currentLocation);
+
+ if (result == nullptr || currentDistance < closestBodyDistance)
+ {
+ result = aBodies[i];
+ closestBodyDistance = currentDistance;
+ }
+ }
+ }
+
+ return result;
+}
+
+//
+// Search our list of body for the one matching our id
+//
+IBody* KinectFaceTracker::FindTrackedBodyById(IBody** aBodies, UINT64 aTrackingId)
+{
+ float closestBodyDistance = std::numeric_limits<float>::max();
+ (void)closestBodyDistance;
+
+ for (int i = 0; i < BODY_COUNT; i++)
+ {
+ BOOLEAN tracked;
+ HRESULT hr = aBodies[i]->get_IsTracked(&tracked);
+
+ if (tracked)
+ {
+ if (SUCCEEDED(hr) && tracked)
+ {
+ UINT64 trackingId = 0;
+ hr = aBodies[i]->get_TrackingId(&trackingId);
+
+ if (SUCCEEDED(hr) && aTrackingId == trackingId)
+ {
+ return aBodies[i];
+ }
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+
+/// <summary>
+/// Processes new face frames
+/// </summary>
+void KinectFaceTracker::ProcessFaces()
+{
+ HRESULT hr=0;
+ IBody* bodies[BODY_COUNT] = { 0 }; // Each bodies will need to be released
+ bool bHaveBodyData = SUCCEEDED(UpdateBodyData(bodies));
+ if (!bHaveBodyData)
+ {
+ return;
+ }
+
+ // Try keep tracking the same body
+ IBody* body = FindTrackedBodyById(bodies, iTrackingId);
+ if (body == nullptr)
+ {
+ // The body we were tracking is gone, try tracking the closest body if any
+ body = FindClosestBody(bodies);
+ if (body != nullptr)
+ {
+ // Update our face source with our new body id
+ hr = body->get_TrackingId(&iTrackingId);
+ if (SUCCEEDED(hr))
+ {
+ // Tell our face source to use the given body id
+ hr = iFaceFrameSource->put_TrackingId(iTrackingId);
+ //OutputDebugStringA("Tracking new body!\n");
+ }
+ }
+ }
+
+ // retrieve the latest face frame from this reader
+ IHighDefinitionFaceFrame* pFaceFrame = nullptr;
+ if (SUCCEEDED(hr))
+ {
+ hr = iFaceFrameReader->AcquireLatestFrame(&pFaceFrame);
+ }
+
+ BOOLEAN bFaceTracked = false;
+ if (SUCCEEDED(hr) && nullptr != pFaceFrame)
+ {
+ // check if a valid face is tracked in this face frame
+ hr = pFaceFrame->get_IsTrackingIdValid(&bFaceTracked);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ if (bFaceTracked)
+ {
+ //OutputDebugStringA("Tracking face!\n");
+
+ //IFaceFrameResult* pFaceFrameResult = nullptr;
+ IFaceAlignment* pFaceAlignment = nullptr;
+ CreateFaceAlignment(&pFaceAlignment); // TODO: check return?
+ //D2D1_POINT_2F faceTextLayout;
+
+ //hr = pFaceFrame->get_FaceFrameResult(&pFaceFrameResult);
+
+ hr = pFaceFrame->GetAndRefreshFaceAlignmentResult(pFaceAlignment);
+
+ // need to verify if pFaceFrameResult contains data before trying to access it
+ if (SUCCEEDED(hr) && pFaceAlignment != nullptr)
+ {
+ hr = pFaceAlignment->get_FaceBoundingBox(&iFaceBox);
+ //pFaceFrameResult->get_FaceBoundingBoxInColorSpace();
+
+ if (SUCCEEDED(hr))
+ {
+ //hr = pFaceFrameResult->GetFacePointsInColorSpace(FacePointType::FacePointType_Count, facePoints);
+ hr = pFaceAlignment->get_HeadPivotPoint(&iFacePosition);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ //hr = pFaceFrameResult->get_FaceRotationQuaternion(&faceRotation);
+ hr = pFaceAlignment->get_FaceOrientation(&iFaceRotationQuaternion);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ //hr = pFaceFrameResult->GetFaceProperties(FaceProperty::FaceProperty_Count, faceProperties);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ //hr = GetFaceTextPositionInColorSpace(ppBodies[0], &faceTextLayout);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ // draw face frame results
+ //m_pDrawDataStreams->DrawFaceFrameResults(0, &faceBox, facePoints, &faceRotation, faceProperties, &faceTextLayout);
+ }
+ }
+
+ SafeRelease(pFaceAlignment);
+ }
+
+ SafeRelease(pFaceFrame);
+ }
+
+ if (bHaveBodyData)
+ {
+ for (int i = 0; i < _countof(bodies); ++i)
+ {
+ SafeRelease(bodies[i]);
+ }
+ }
+}
+
+
diff --git a/tracker-kinect-face/kinect_face_tracker.h b/tracker-kinect-face/kinect_face_tracker.h
new file mode 100644
index 00000000..83b58d71
--- /dev/null
+++ b/tracker-kinect-face/kinect_face_tracker.h
@@ -0,0 +1,121 @@
+/* Copyright (c) 2019, Stephane Lenclud <github@lenclud.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 <cmath>
+
+#include "api/plugin-api.hpp"
+#include "compat/timer.hpp"
+#include "video/video-widget.hpp"
+
+// Kinect Header files
+#include <Kinect.h>
+#include <Kinect.Face.h>
+
+// @deprecated Use UniqueInterface instead. Remove it at some point.
+template<class Interface>
+inline void SafeRelease(Interface *& pInterfaceToRelease)
+{
+ if (pInterfaceToRelease != nullptr)
+ {
+ pInterfaceToRelease->Release();
+ pInterfaceToRelease = nullptr;
+ }
+}
+
+template<class Interface>
+inline void ReleaseInterface(Interface* pInterfaceToRelease)
+{
+ if (pInterfaceToRelease != nullptr)
+ {
+ pInterfaceToRelease->Release();
+ }
+}
+
+// Safely use Microsoft interfaces.
+template<typename T>
+class UniqueInterface : public std::unique_ptr<T, decltype(&ReleaseInterface<T>)> ///**/
+{
+public:
+ UniqueInterface() : std::unique_ptr<T, decltype(&ReleaseInterface<T>)>(nullptr, ReleaseInterface<T>){}
+ // Access pointer, typically for creation
+ T** PtrPtr() { return &iPtr; };
+ // Called this once the pointer was created
+ void Reset() { std::unique_ptr<T, decltype(&ReleaseInterface<T>)>::reset(iPtr); }
+ // If ever you want to release that interface before the object is deleted
+ void Free() { iPtr = nullptr; Reset(); }
+private:
+ T* iPtr = nullptr;
+};
+
+
+//
+//
+//
+class KinectFaceTracker : public ITracker
+{
+public:
+ KinectFaceTracker();
+ ~KinectFaceTracker() override;
+ module_status start_tracker(QFrame* aFrame) override;
+ void data(double *data) override;
+ bool center() override;
+
+private:
+
+
+ // Kinect stuff
+ void Update();
+ HRESULT InitializeDefaultSensor();
+ void ProcessFaces();
+ HRESULT UpdateBodyData(IBody** ppBodies);
+ void ExtractFaceRotationInDegrees(const Vector4* pQuaternion, float* pPitch, float* pYaw, float* pRoll);
+ static IBody* FindClosestBody(IBody** aBodies);
+ static IBody* FindTrackedBodyById(IBody** aBodies,UINT64 aTrackingId);
+
+ //
+ Timer iTimer;
+
+ // Current Kinect
+ IKinectSensor* iKinectSensor = nullptr;
+
+ // Color reader
+ IColorFrameReader* iColorFrameReader = nullptr;
+
+ // Body reader
+ IBodyFrameReader* iBodyFrameReader = nullptr;
+
+ // Face sources
+ IHighDefinitionFaceFrameSource* iFaceFrameSource = nullptr;
+
+ // Face readers
+ IHighDefinitionFaceFrameReader* iFaceFrameReader = nullptr;
+
+ //
+ RGBQUAD* iColorRGBX = nullptr;
+
+ RectI iFaceBox = { 0 };
+
+ // Face position
+ CameraSpacePoint iLastFacePosition = { 0 };
+ CameraSpacePoint iFacePosition = { 0 };
+ CameraSpacePoint iFacePositionCenter = { 0 };
+
+ Vector4 iFaceRotationQuaternion = { 0 };
+ // As Yaw, Pitch, Roll
+ CameraSpacePoint iLastFaceRotation = { 0 };
+ CameraSpacePoint iFaceRotation = { 0 };
+ CameraSpacePoint iFaceRotationCenter = { 0 };
+ //
+ std::unique_ptr<video_widget> iVideoWidget;
+ std::unique_ptr<QLayout> iLayout;
+
+ // Id of the body currently being tracked
+ UINT64 iTrackingId = 0;
+};
diff --git a/tracker-kinect-face/lang/nl_NL.ts b/tracker-kinect-face/lang/nl_NL.ts
new file mode 100644
index 00000000..fbe86bb9
--- /dev/null
+++ b/tracker-kinect-face/lang/nl_NL.ts
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>KinectFaceMetadata</name>
+ <message>
+ <source>Kinect Face 0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>KinectFaceSettings</name>
+ <message>
+ <source>Kinect license</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>KinectFaceUi</name>
+ <message>
+ <source>Kinect Face Tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;justify&quot;&gt;Start OpenTrack to check if Kinect Face Tracker is working.&lt;/p&gt;&lt;p align=&quot;justify&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note&lt;/span&gt;: When using OpenTrack with a Kinect for Windows v2 Sensor, Microsoft will collect telemetry data (e.g. operating system, number of processors, graphic chipset, memory, device type, locale, time) in order to improve Microsoft products and services. The data will not be used to identify specific individuals.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-kinect-face/lang/ru_RU.ts b/tracker-kinect-face/lang/ru_RU.ts
new file mode 100644
index 00000000..678fa06c
--- /dev/null
+++ b/tracker-kinect-face/lang/ru_RU.ts
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>KinectFaceMetadata</name>
+ <message>
+ <source>Kinect Face 0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>KinectFaceSettings</name>
+ <message>
+ <source>Kinect license</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>KinectFaceUi</name>
+ <message>
+ <source>Kinect Face Tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;justify&quot;&gt;Start OpenTrack to check if Kinect Face Tracker is working.&lt;/p&gt;&lt;p align=&quot;justify&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note&lt;/span&gt;: When using OpenTrack with a Kinect for Windows v2 Sensor, Microsoft will collect telemetry data (e.g. operating system, number of processors, graphic chipset, memory, device type, locale, time) in order to improve Microsoft products and services. The data will not be used to identify specific individuals.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-kinect-face/lang/stub.ts b/tracker-kinect-face/lang/stub.ts
new file mode 100644
index 00000000..de0e4f95
--- /dev/null
+++ b/tracker-kinect-face/lang/stub.ts
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>KinectFaceMetadata</name>
+ <message>
+ <source>Kinect Face 0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>KinectFaceSettings</name>
+ <message>
+ <source>Kinect license</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>KinectFaceUi</name>
+ <message>
+ <source>Kinect Face Tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;justify&quot;&gt;Start OpenTrack to check if Kinect Face Tracker is working.&lt;/p&gt;&lt;p align=&quot;justify&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note&lt;/span&gt;: When using OpenTrack with a Kinect for Windows v2 Sensor, Microsoft will collect telemetry data (e.g. operating system, number of processors, graphic chipset, memory, device type, locale, time) in order to improve Microsoft products and services. The data will not be used to identify specific individuals.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-kinect-face/lang/zh_CN.ts b/tracker-kinect-face/lang/zh_CN.ts
new file mode 100644
index 00000000..1e1b55d1
--- /dev/null
+++ b/tracker-kinect-face/lang/zh_CN.ts
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>KinectFaceMetadata</name>
+ <message>
+ <source>Kinect Face 0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>KinectFaceSettings</name>
+ <message>
+ <source>Kinect license</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>KinectFaceUi</name>
+ <message>
+ <source>Kinect Face Tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;justify&quot;&gt;Start OpenTrack to check if Kinect Face Tracker is working.&lt;/p&gt;&lt;p align=&quot;justify&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note&lt;/span&gt;: When using OpenTrack with a Kinect for Windows v2 Sensor, Microsoft will collect telemetry data (e.g. operating system, number of processors, graphic chipset, memory, device type, locale, time) in order to improve Microsoft products and services. The data will not be used to identify specific individuals.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-linux-joystick/CMakeLists.txt b/tracker-linux-joystick/CMakeLists.txt
new file mode 100644
index 00000000..4e821b01
--- /dev/null
+++ b/tracker-linux-joystick/CMakeLists.txt
@@ -0,0 +1,4 @@
+if(LINUX)
+ otr_module(tracker-linux-joystick)
+ target_link_libraries(opentrack-tracker-linux-joystick)
+endif()
diff --git a/tracker-linux-joystick/ftnoir_tracker_linux_joystick.cpp b/tracker-linux-joystick/ftnoir_tracker_linux_joystick.cpp
new file mode 100644
index 00000000..54d9b059
--- /dev/null
+++ b/tracker-linux-joystick/ftnoir_tracker_linux_joystick.cpp
@@ -0,0 +1,87 @@
+/* Copyright (c) 2013 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * 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 "ftnoir_tracker_linux_joystick.h"
+#include "api/plugin-api.hpp"
+#include "compat/math.hpp"
+#include <QMutexLocker>
+
+joystick::joystick()
+{
+ QString device = getJoystickDevice(s.guid);
+ joy_fd = open(device.toUtf8().data(), O_RDONLY | O_NONBLOCK);
+}
+
+
+joystick::~joystick() {
+ if (joy_fd > 0) close(joy_fd);
+}
+
+module_status joystick::start_tracker(QFrame *)
+{
+ if (joy_fd == -1) return error("Couldn't open joystick");
+ return status_ok();
+}
+
+
+void joystick::data(double *data)
+{
+ int map[6] = {
+ s.joy_1 - 1,
+ s.joy_2 - 1,
+ s.joy_3 - 1,
+ s.joy_4 - 1,
+ s.joy_5 - 1,
+ s.joy_6 - 1,
+ };
+
+ const double limits[] = {
+ 100,
+ 100,
+ 100,
+ 180,
+ 180,
+ 180
+ };
+
+ const QString guid = s.guid;
+ int axes[8];
+ struct js_event event;
+ bool ret = true;
+ if (read(joy_fd, &event, sizeof(event)) > 0)
+ {
+ switch (event.type)
+ {
+ case JS_EVENT_AXIS:
+ if (event.number >= 8) break;
+ axes_state[event.number] = event.value;
+
+ break;
+ default:
+ /* Ignore init/button events. */
+ break;
+ }
+ }
+
+ for (int i = 0; i < 6; i++)
+ {
+ axes[i] = axes_state[i];
+ }
+ if (ret)
+ {
+ for (int i = 0; i < 6; i++)
+ {
+ int k = map[i];
+ if (k < 0 || k >= 8)
+ data[i] = 0;
+ else
+ data[i] = std::clamp(axes[k] * limits[i] / AXIS_MAX,
+ -limits[i], limits[i]);
+ }
+ }
+}
+
+OPENTRACK_DECLARE_TRACKER(joystick, dialog_joystick, joystickDll)
diff --git a/tracker-linux-joystick/ftnoir_tracker_linux_joystick.h b/tracker-linux-joystick/ftnoir_tracker_linux_joystick.h
new file mode 100644
index 00000000..6ddc4909
--- /dev/null
+++ b/tracker-linux-joystick/ftnoir_tracker_linux_joystick.h
@@ -0,0 +1,90 @@
+/* Copyright (c) 2013 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * 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 "ui_ftnoir_tracker_linux_joystick_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 <linux/joystick.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "options/options.hpp"
+using namespace options;
+
+struct settings : opts {
+ value<QString> guid;
+ value<int> joy_1, joy_2, joy_3, joy_4, joy_5, joy_6;
+ settings() :
+ opts("tracker-linux-joystick"),
+ guid(b, "joy-guid", ""),
+ joy_1(b, "axis-map-1", 1),
+ joy_2(b, "axis-map-2", 2),
+ joy_3(b, "axis-map-3", 3),
+ joy_4(b, "axis-map-4", 4),
+ joy_5(b, "axis-map-5", 5),
+ joy_6(b, "axis-map-6", 6)
+ {}
+};
+
+struct linux_joystick {
+ QString name;
+ QString device_id;
+ QString dev;
+};
+QList<linux_joystick> getJoysticks();
+QString getJoystickDevice(QString guid);
+
+class joystick : public ITracker
+{
+public:
+ joystick();
+ ~joystick();
+ module_status start_tracker(QFrame *);
+ void data(double *data);
+ settings s;
+ QString guid;
+ static constexpr int AXIS_MAX = USHRT_MAX;
+ int axes_state[6] = {0};
+ int joy_fd;
+};
+
+class dialog_joystick: public ITrackerDialog
+{
+ Q_OBJECT
+public:
+ dialog_joystick();
+ void register_tracker(ITracker *) {}
+ void unregister_tracker() {}
+ Ui::UILinuxJoystickControls ui;
+ joystick* tracker;
+ settings s;
+ struct joys {
+ QString name;
+ QString guid;
+ };
+ QList<joys> joys_;
+private slots:
+ void doOK();
+ void doCancel();
+};
+
+class joystickDll : public Metadata
+{
+ Q_OBJECT
+
+ QString name() { return tr("Linux Joystick input"); }
+ QIcon icon() { return QIcon(":/images/opentrack.png"); }
+};
diff --git a/tracker-linux-joystick/ftnoir_tracker_linux_joystick_controls.ui b/tracker-linux-joystick/ftnoir_tracker_linux_joystick_controls.ui
new file mode 100644
index 00000000..2a54c74a
--- /dev/null
+++ b/tracker-linux-joystick/ftnoir_tracker_linux_joystick_controls.ui
@@ -0,0 +1,492 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UILinuxJoystickControls</class>
+ <widget class="QWidget" name="UILinuxJoystickControls">
+ <property name="windowModality">
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>498</width>
+ <height>334</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Tracker 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="QFrame" name="frame">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Device</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="joylist">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Mapping</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QComboBox" name="joy_1">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #1</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #2</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #4</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #5</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #6</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #7</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #8</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="joy_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>2</number>
+ </property>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #1</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #2</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #4</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #5</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #6</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #7</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #8</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="joy_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>3</number>
+ </property>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #1</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #2</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #4</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #5</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #6</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #7</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #8</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QComboBox" name="joy_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>4</number>
+ </property>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #1</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #2</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #4</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #5</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #6</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #7</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #8</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QComboBox" name="joy_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>5</number>
+ </property>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #1</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #2</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #4</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #5</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #6</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #7</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #8</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QComboBox" name="joy_6">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>6</number>
+ </property>
+ <item>
+ <property name="text">
+ <string>Disabled</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #1</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #2</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #4</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #5</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #6</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #7</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Joystick axis #8</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>X</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <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-linux-joystick/ftnoir_tracker_linux_joystick_dialog.cpp b/tracker-linux-joystick/ftnoir_tracker_linux_joystick_dialog.cpp
new file mode 100644
index 00000000..1cf75bc1
--- /dev/null
+++ b/tracker-linux-joystick/ftnoir_tracker_linux_joystick_dialog.cpp
@@ -0,0 +1,40 @@
+#include "ftnoir_tracker_linux_joystick.h"
+#include "api/plugin-api.hpp"
+
+dialog_joystick::dialog_joystick() : 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()));
+
+ QList<::linux_joystick> joysticks = getJoysticks();
+
+ for (int i = 0; i < joysticks.size(); i++) {
+ ::linux_joystick joy = joysticks[i];
+ joys_.push_back(joys { joy.name, joy.device_id});
+ ui.joylist->addItem(QString("%1 | %2").arg(joy.dev).arg(joy.name));
+ if (joysticks[i].device_id == s.guid) ui.joylist->setCurrentIndex(i);
+ }
+
+ tie_setting(s.joy_1, ui.joy_1);
+ tie_setting(s.joy_2, ui.joy_2);
+ tie_setting(s.joy_3, ui.joy_3);
+ tie_setting(s.joy_4, ui.joy_4);
+ tie_setting(s.joy_5, ui.joy_5);
+ tie_setting(s.joy_6, ui.joy_6);
+}
+
+void dialog_joystick::doOK() {
+ int idx = ui.joylist->currentIndex();
+ static const joys def { {}, {} };
+ auto val = joys_.value(idx, def);
+ s.guid = val.guid;
+ s.b->save();
+ close();
+}
+
+void dialog_joystick::doCancel() {
+ close();
+}
diff --git a/tracker-linux-joystick/lang/de_DE.ts b/tracker-linux-joystick/lang/de_DE.ts
new file mode 100644
index 00000000..9f03ffe9
--- /dev/null
+++ b/tracker-linux-joystick/lang/de_DE.ts
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>UILinuxJoystickControls</name>
+ <message>
+ <source>Tracker settings</source>
+ <translation>Tracker-Einstellungen</translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation>Gerät</translation>
+ </message>
+ <message>
+ <source>Mapping</source>
+ <translation>Abbildung</translation>
+ </message>
+ <message>
+ <source>Disabled</source>
+ <translation>Ausgeschaltet</translation>
+ </message>
+ <message>
+ <source>Joystick axis #1</source>
+ <translation>Joystick-Achse #1</translation>
+ </message>
+ <message>
+ <source>Joystick axis #2</source>
+ <translation>Joystick-Achse #2</translation>
+ </message>
+ <message>
+ <source>Joystick axis #3</source>
+ <translation>Joystick-Achse #3</translation>
+ </message>
+ <message>
+ <source>Joystick axis #4</source>
+ <translation>Joystick-Achse #4</translation>
+ </message>
+ <message>
+ <source>Joystick axis #5</source>
+ <translation>Joystick-Achse #5</translation>
+ </message>
+ <message>
+ <source>Joystick axis #6</source>
+ <translation>Joystick-Achse #6</translation>
+ </message>
+ <message>
+ <source>Joystick axis #7</source>
+ <translation>Joystick-Achse #7</translation>
+ </message>
+ <message>
+ <source>Joystick axis #8</source>
+ <translation>Joystick-Achse #8</translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation>Z</translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation>Gieren</translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation>Nicken</translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation>Rollen</translation>
+ </message>
+</context>
+<context>
+ <name>joystickDll</name>
+ <message>
+ <source>Linux Joystick input</source>
+ <translation>Linux-Joystick-Eingabe</translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-linux-joystick/lang/nl_NL.ts b/tracker-linux-joystick/lang/nl_NL.ts
new file mode 100644
index 00000000..1c9b89d0
--- /dev/null
+++ b/tracker-linux-joystick/lang/nl_NL.ts
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>UILinuxJoystickControls</name>
+ <message>
+ <source>Tracker settings</source>
+ <translation type="unfinished">Tracker-instellingen</translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation type="unfinished">Apparaat</translation>
+ </message>
+ <message>
+ <source>Mapping</source>
+ <translation type="unfinished">Verwijzing</translation>
+ </message>
+ <message>
+ <source>Disabled</source>
+ <translation type="unfinished">Uitgeschakeld</translation>
+ </message>
+ <message>
+ <source>Joystick axis #1</source>
+ <translation type="unfinished">Joystick-as #1</translation>
+ </message>
+ <message>
+ <source>Joystick axis #2</source>
+ <translation type="unfinished">Joystick-as #2</translation>
+ </message>
+ <message>
+ <source>Joystick axis #3</source>
+ <translation type="unfinished">Joystick-as #3</translation>
+ </message>
+ <message>
+ <source>Joystick axis #4</source>
+ <translation type="unfinished">Joystick-as #4</translation>
+ </message>
+ <message>
+ <source>Joystick axis #5</source>
+ <translation type="unfinished">Joystick-as #5</translation>
+ </message>
+ <message>
+ <source>Joystick axis #6</source>
+ <translation type="unfinished">Joystick-as #6</translation>
+ </message>
+ <message>
+ <source>Joystick axis #7</source>
+ <translation type="unfinished">Joystick-as #7</translation>
+ </message>
+ <message>
+ <source>Joystick axis #8</source>
+ <translation type="unfinished">Joystick-as #8</translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished">X</translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished">Y</translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished">Z</translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation type="unfinished">Yaw</translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation type="unfinished">Pitch</translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation type="unfinished">Rol</translation>
+ </message>
+</context>
+<context>
+ <name>joystickDll</name>
+ <message>
+ <source>Linux Joystick input</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-linux-joystick/lang/ru_RU.ts b/tracker-linux-joystick/lang/ru_RU.ts
new file mode 100644
index 00000000..34ed1089
--- /dev/null
+++ b/tracker-linux-joystick/lang/ru_RU.ts
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>UILinuxJoystickControls</name>
+ <message>
+ <source>Tracker settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Disabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #3</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #4</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #5</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #6</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #7</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #8</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>joystickDll</name>
+ <message>
+ <source>Linux Joystick input</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-linux-joystick/lang/stub.ts b/tracker-linux-joystick/lang/stub.ts
new file mode 100644
index 00000000..12dc1400
--- /dev/null
+++ b/tracker-linux-joystick/lang/stub.ts
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>UILinuxJoystickControls</name>
+ <message>
+ <source>Tracker settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Disabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #3</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #4</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #5</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #6</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #7</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #8</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>joystickDll</name>
+ <message>
+ <source>Linux Joystick input</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-linux-joystick/lang/zh_CN.ts b/tracker-linux-joystick/lang/zh_CN.ts
new file mode 100644
index 00000000..e7813c3a
--- /dev/null
+++ b/tracker-linux-joystick/lang/zh_CN.ts
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>UILinuxJoystickControls</name>
+ <message>
+ <source>Tracker settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Disabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #3</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #4</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #5</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #6</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #7</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Joystick axis #8</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>joystickDll</name>
+ <message>
+ <source>Linux Joystick input</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-linux-joystick/linux_joystick.cpp b/tracker-linux-joystick/linux_joystick.cpp
new file mode 100644
index 00000000..49718b52
--- /dev/null
+++ b/tracker-linux-joystick/linux_joystick.cpp
@@ -0,0 +1,65 @@
+#include "ftnoir_tracker_linux_joystick.h"
+
+#include <QDir>
+#include <QFileInfo>
+#include <QVariant>
+
+// Discovery is done by searching for devices in the sys file system.
+//
+// Given a path like this
+// /sys/devices/pci0000:00/0000:00:14.0/usb1/1-10/1-10:1.2/0003:2341:8036.0170/input/input380/js0
+// we want to get this part of the string 2341:8036, it will allow us to
+// identify the device in the future.
+// alternative way of doing this https://stackoverflow.com/questions/21173988/linux-attempting-to-get-joystick-vendor-and-product-ids-via-ioctl-get-einval-i
+std::tuple<QString, QString> sysfsDeviceToJsDev(QFileInfo device) {
+ using ret = std::tuple<QString, QString>;
+ QString symlink = device.symLinkTarget();
+ QString js_dev = QString("/dev/input/%1").arg(device.fileName());
+
+ QRegExp sep(QString("[:.%1]").arg(QDir::separator()));
+ QString device_id = symlink.section(sep, -6, -5);
+ return ret(js_dev, device_id);
+}
+
+QList<linux_joystick> getJoysticks()
+{
+ char name[128];
+ QList<linux_joystick> joysticks;
+
+ QDir dir("/sys/class/input/");
+ dir.setNameFilters({ "js*" });
+ QFileInfoList list = dir.entryInfoList();
+ for (int i = 0; i < list.size(); ++i)
+ {
+ QFileInfo device = list.at(i);
+ auto [js_dev, device_id] = sysfsDeviceToJsDev(device);
+ int iFile = open(js_dev.toUtf8().data(), O_RDONLY | O_NONBLOCK);
+ if (iFile == -1) continue;
+ if (ioctl(iFile, JSIOCGNAME(sizeof(name)), &name) > 0)
+ {
+ linux_joystick j;
+ j.name = name;
+ j.dev = js_dev;
+ j.device_id = device_id;
+ joysticks.append(j);
+ }
+ close(iFile);
+
+ }
+
+ return joysticks;
+}
+
+QString getJoystickDevice(QString guid) {
+ QDir dir("/sys/class/input/");
+ dir.setNameFilters({ "js*" });
+ QFileInfoList list = dir.entryInfoList();
+ for (int i = 0; i < list.size(); ++i)
+ {
+ QFileInfo device = list.at(i);
+ auto [js_dev, device_id] = sysfsDeviceToJsDev(device);
+ if (device_id == guid) return js_dev;
+ }
+
+ return {};
+}
diff --git a/tracker-neuralnet/BUILD.md b/tracker-neuralnet/BUILD.md
new file mode 100644
index 00000000..b8994b00
--- /dev/null
+++ b/tracker-neuralnet/BUILD.md
@@ -0,0 +1,20 @@
+ONNX Runtime
+------------
+
+Recommended approach on Windws: Build a shared library from sources. Use static MSVC
+runtime library. The v1.6.0 branch should work fine.
+
+Source location: https://github.com/microsoft/onnxruntime
+
+In order to build, execute `build.bat` as follows:
+
+```
+$ build.bat --config Release --x86 --cmake_extra_defines CMAKE_INSTALL_PREFIX="D:\Dev\onnxruntime-x86-release" --build_dir .\buildx86\ --enable_msvc_static_runtime --build_shared_lib --skip_tests --cmake_generator "Visual Studio 15 2017"
+$ cmake --install .\buildx64\Release
+```
+
+Replace the argument for `--cmake_generator` if needed. Also adjust the build-and install directories.
+
+This should place all required files in the directory specified by CMAKE_INSTALL_PREFIX.
+
+See also https://www.onnxruntime.ai/docs/how-to/build.html. \ No newline at end of file
diff --git a/tracker-neuralnet/CMakeLists.txt b/tracker-neuralnet/CMakeLists.txt
new file mode 100644
index 00000000..3729c789
--- /dev/null
+++ b/tracker-neuralnet/CMakeLists.txt
@@ -0,0 +1,49 @@
+include(opentrack-opencv)
+set(host-spec "${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_PROCESSOR} ${CMAKE_SIZEOF_VOID_P}")
+if(host-spec MATCHES "^Linux i[3-6]86 4$")
+ return()
+endif()
+
+find_package(OpenCV QUIET)
+find_package(OpenMP QUIET) # Used to control number of onnx threads.
+find_package(ONNXRuntime QUIET)
+
+if(OpenCV_FOUND AND ONNXRuntime_FOUND AND OpenMP_FOUND)
+ if(MSVC)
+ add_compile_options(-EHsc)
+ add_definitions(-D_HAS_EXCEPTIONS=1)
+ endif()
+
+ otr_module(tracker-neuralnet)
+
+ target_link_libraries(${self}
+ opentrack-cv
+ onnxruntime::onnxruntime
+ opencv_calib3d
+ opencv_imgproc
+ opencv_imgcodecs
+ opencv_core
+ OpenMP::OpenMP_CXX
+ )
+
+ # OpenMP::OpenMP_CXX doesn't set up the -fopenmp linking option, so set it up ourselves.
+ if(NOT MSVC)
+ target_link_options(${self} PUBLIC ${OpenMP_CXX_FLAGS})
+ endif()
+
+ install(
+ FILES "models/head-localizer.onnx"
+ "models/head-pose-0.2-big.onnx"
+ "models/head-pose-0.2-small.onnx"
+ "models/head-pose-0.3-big-quantized.onnx"
+ DESTINATION "${opentrack-libexec}/models"
+ PERMISSIONS ${opentrack-perms-file}
+ )
+
+ if(WIN32)
+ otr_install_lib("${ONNXRuntime_RUNTIME}" ".")
+ endif()
+ if(MSVC)
+ otr_install_lib("redist/vcomp140.dll" "${opentrack-bin}")
+ endif()
+endif()
diff --git a/tracker-neuralnet/deadzone_filter.cpp b/tracker-neuralnet/deadzone_filter.cpp
new file mode 100644
index 00000000..fa96eeb3
--- /dev/null
+++ b/tracker-neuralnet/deadzone_filter.cpp
@@ -0,0 +1,173 @@
+#include "deadzone_filter.h"
+#include "model_adapters.h"
+#include "opencv_contrib.h"
+#include "unscented_trafo.h"
+
+#include <tuple>
+#include <opencv2/core/base.hpp>
+#include <opencv2/core/matx.hpp>
+#include <opencv2/core/quaternion.hpp>
+
+namespace neuralnet_tracker_ns
+{
+
+using namespace cvcontrib;
+
+// Number of degrees of freedom of position and rotation
+static constexpr int dofs = 6;
+
+using StateVec = cv::Vec<float,dofs>;
+using StateCov = cv::Matx<float,dofs,dofs>;
+
+static constexpr int num_sigmas = ukf_cv::MerweScaledSigmaPoints<dofs>::num_sigmas;
+// Rescaling factor for position/size living in the space of the face crop.
+// Applied prior to application of UKF to prevent numerical problems.
+static constexpr float img_scale = 200.f;
+// Similar rescaling factor for position/size that live in world space.
+static constexpr float world_scale = 1000.f; // mm
+
+// Fills the 6 DoF covariance factor, as in L L^T factorization.
+// Covariance is given wrt the tangent space of current predictions
+StateCov make_tangent_space_uncertainty_tril(const PoseEstimator::Face &face)
+{
+ StateCov tril = StateCov::eye();
+ set_minor<3,3>(tril, 0, 0, face.center_size_cov_tril / img_scale);
+ set_minor<3,3>(tril, 3, 3, face.rotaxis_cov_tril);
+ return tril;
+}
+
+
+QuatPose apply_offset(const QuatPose& pose, const StateVec& offset)
+{
+ // Unpack
+ const cv::Vec3f dp = { offset[0], offset[1], offset[2] };
+ const cv::Quatf dr = cv::Quatf::createFromRvec(cv::Vec3f{ offset[3], offset[4], offset[5] });
+ const auto p = pose.pos + dp;
+ const auto r = pose.rot * dr;
+ return { r, p };
+}
+
+
+std::tuple<cv::Quatf, cv::Point2f, float> apply_offset(const PoseEstimator::Face& face, const StateVec& offset)
+{
+ const cv::Quatf dr = cv::Quatf::createFromRvec(cv::Vec3f{ offset[3], offset[4], offset[5] });
+ const auto r = face.rotation * dr;
+
+ const cv::Point2f p = {
+ face.center.x + offset[0]*img_scale,
+ face.center.y + offset[1]*img_scale
+ };
+
+ // Intercept the case where the head size stddev became so large that the sigma points
+ // were created with negative head size (mean - constant*stddev ...). Negative head size
+ // is bad. But this is fine. The unscented transform where this function comes into play
+ // is designed to handle non-linearities like this.
+ const float sz = std::max(0.1f*face.size, face.size + offset[2]*img_scale);
+
+ return {
+ r,
+ p,
+ sz,
+ };
+}
+
+
+StateVec relative_to(const QuatPose& reference, const QuatPose& pose)
+{
+ const auto p = pose.pos - reference.pos;
+ const auto r = toRotVec(reference.rot.conjugate()*pose.rot);
+ return StateVec{ p[0], p[1], p[2], r[0], r[1], r[2] };
+}
+
+
+ukf_cv::SigmaPoints<dofs> relative_to(const QuatPose& pose, const std::array<QuatPose,num_sigmas>& sigmas)
+{
+ ukf_cv::SigmaPoints<dofs> out; // Beware, the number of points is != the number of DoFs.
+ std::transform(sigmas.begin(), sigmas.end(), out.begin(), [&pose](const QuatPose& s) {
+ return relative_to(pose, s);
+ });
+ return out;
+}
+
+
+std::array<QuatPose,num_sigmas> compute_world_pose_from_sigma_point(const PoseEstimator::Face& face, const ukf_cv::SigmaPoints<dofs>& sigmas, Face2WorldFunction face2world)
+{
+ std::array<QuatPose,num_sigmas> out;
+ std::transform(sigmas.begin(), sigmas.end(), out.begin(), [face2world=std::move(face2world), &face](const StateVec& sigma_point) {
+ // First unpack the state vector and generate quaternion rotation w.r.t image space.
+ const auto [rotation, center, size] = apply_offset(face, sigma_point);
+ // Then transform ...
+ QuatPose pose = face2world(rotation, center, size);
+ pose.pos /= world_scale;
+ return pose;
+ });
+ return out;
+}
+
+
+StateVec apply_filter_to_offset(const StateVec& offset, const StateCov& offset_cov, float, const FiltParams& params)
+{
+ // Offset and Cov represent a multivariate normal distribution, which is the probability of the new pose measured w.r.t the previous one.
+ // Prob(x) ~exp(-(x-mu)t Cov^-1 (x-mu))
+ // We want to attenuate this offset, or zero it out completely, to obtain a deadzone-filter behaviour. The size of the deadzone shall be
+ // determined by the covariance projected to the offset direction like so:
+ // Take x = mu - mu / |mu| * alpha
+ // p(alpha) ~exp(-alpha^2 / |mu|^2 * mut Cov^-1 mu) = ~exp(-alpha^2 / sigma^2) with sigma^2 = mut Cov^-1 mu / |mu|^2.
+ // So this projection is like a 1d normal distribution with some standard deviation, which we take to scale the deadzone.
+
+ bool ok = true;
+
+ const float len_div_sigma_sqr = offset.dot(offset_cov.inv(cv::DECOMP_CHOLESKY, &ok) * offset);
+
+ const float attenuation = (ok) ? sigmoid((std::sqrt(len_div_sigma_sqr) - params.deadzone_size)*params.deadzone_hardness) : 1.f;
+
+ // {
+ // std::cout << "cov diag: " << offset_cov.diag() << std::endl;
+ // std::cout << "offset: " << cv::norm(offset) << std::endl;
+ // std::cout << "len_div_sigma_sqr: " << cv::norm(len_div_sigma_sqr) << std::endl;
+ // std::cout << "attenuation (" << ok << "): " << attenuation << std::endl;
+ // }
+
+ return offset*attenuation;
+}
+
+
+QuatPose apply_filter(const PoseEstimator::Face &face, const QuatPose& previous_pose_, float dt, Face2WorldFunction face2world, const FiltParams& params)
+{
+ ukf_cv::MerweScaledSigmaPoints<dofs> unscentedtrafo;
+ auto previous_pose = previous_pose_;
+ previous_pose.pos /= world_scale;
+
+ // Get 6 DoF covariance factor for the predictions in the face crop space.
+ const auto cov_tril = make_tangent_space_uncertainty_tril(face);
+
+ // Compute so called sigma points. These represent the distribution from the covariance matrix in terms of
+ // sampling points.
+ const ukf_cv::SigmaPoints<dofs> sigmas = unscentedtrafo.compute_sigmas(to_vec(StateVec::zeros()), cov_tril, true);
+
+ // The filter uses an unscented transform to translate that into a distribution for the offset from the previous pose.
+ // The trick is to transform the sampling points and compute a covariance from them in the output space.
+ // We have many of these sigma points. This is why that callback comes into play here.
+ // The transform to 3d world space is more than Face2WorldFunction because we also need to apply the sigma point (as
+ // a relative offset) to the pose in face crop space.
+ const std::array<QuatPose,num_sigmas> pose_sigmas = compute_world_pose_from_sigma_point(face, sigmas, std::move(face2world));
+
+ // Compute sigma points relative to the previous pose
+ const ukf_cv::SigmaPoints<dofs> deltas_sigmas = relative_to(previous_pose, pose_sigmas);
+
+ // Compute the mean offset from the last pose and the spread due to the networks uncertainty output.
+ const auto [offset, offset_cov] = unscentedtrafo.compute_statistics(deltas_sigmas);
+
+ // Then the deadzone is applied to the offset and finally the previous pose is transformed by the offset to arrive
+ // at the final output.
+ const StateVec scaled_offset = apply_filter_to_offset(offset, offset_cov, dt, params);
+
+ QuatPose new_pose = apply_offset(previous_pose, scaled_offset);
+
+ new_pose.pos *= world_scale;
+
+ return new_pose;
+}
+
+
+} // namespace neuralnet_tracker_ns \ No newline at end of file
diff --git a/tracker-neuralnet/deadzone_filter.h b/tracker-neuralnet/deadzone_filter.h
new file mode 100644
index 00000000..a9b6aada
--- /dev/null
+++ b/tracker-neuralnet/deadzone_filter.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "unscented_trafo.h"
+#include "opencv_contrib.h"
+#include "model_adapters.h"
+
+namespace neuralnet_tracker_ns
+{
+
+/// Represents a 6d pose by quaternion rotation and position vector.
+struct QuatPose {
+ cv::Quatf rot;
+ cv::Vec3f pos;
+};
+
+struct FiltParams
+{
+ float deadzone_hardness = 1.f;
+ float deadzone_size = 1.f;
+};
+
+/** Callback type for converting data from the `Face` struct to a 6d pose.
+*
+* This callback is needed because it depends on things that the filter doesn't have to know about and it is called multiple times
+* due to the way how uncertainty estimates are handled
+*/
+using Face2WorldFunction = std::function<QuatPose (const cv::Quatf&, const cv::Point2f&, float)>;
+
+/** Applies a deadzone filter similar to the one used in the Hamilton filter.
+*
+* What sets this apart is that the deadzone size scales with the uncertainty estimate of the network.
+* The rotation uncertainty is represented by a covariance matrix for the distribution of a rotation vector which
+* describes the offset from the mean rotation (the quaternion in the `Face` struct).
+*/
+QuatPose apply_filter(const PoseEstimator::Face &face, const QuatPose& previous_pose, float dt, Face2WorldFunction face2world, const FiltParams& params);
+
+} // namespace neuralnet_tracker_ns \ No newline at end of file
diff --git a/tracker-neuralnet/ftnoir_tracker_neuralnet.cpp b/tracker-neuralnet/ftnoir_tracker_neuralnet.cpp
new file mode 100644
index 00000000..c55ddf0c
--- /dev/null
+++ b/tracker-neuralnet/ftnoir_tracker_neuralnet.cpp
@@ -0,0 +1,976 @@
+/* Copyright (c) 2021 Michael Welter <michael@welter-4d.de>
+ *
+ * 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 "ftnoir_tracker_neuralnet.h"
+#include "deadzone_filter.h"
+#include "opencv_contrib.h"
+
+#include "compat/sleep.hpp"
+#include "compat/math-imports.hpp"
+#include "compat/timer.hpp"
+#include "compat/check-visible.hpp"
+#include "cv/init.hpp"
+
+#include <omp.h>
+#include <onnxruntime_cxx_api.h>
+#include <opencv2/core.hpp>
+#include <opencv2/core/quaternion.hpp>
+
+#ifdef _MSC_VER
+# pragma warning(disable : 4702)
+#endif
+
+#include <QMutexLocker>
+#include <QDebug>
+#include <QFile>
+#include <QFileDialog>
+#include <QFileInfo>
+
+#include <cstdio>
+#include <cmath>
+#include <algorithm>
+#include <chrono>
+#include <string>
+#include <stdexcept>
+#include <unordered_map>
+
+// Some demo code for onnx
+// https://github.com/microsoft/onnxruntime/blob/master/csharp/test/Microsoft.ML.OnnxRuntime.EndToEndTests.Capi/C_Api_Sample.cpp
+// https://github.com/leimao/ONNX-Runtime-Inference/blob/main/src/inference.cpp
+
+namespace neuralnet_tracker_ns
+{
+
+using namespace cvcontrib;
+
+using numeric_types::vec3;
+using numeric_types::vec2;
+using numeric_types::mat33;
+
+#if _MSC_VER
+std::wstring convert(const QString &s) { return s.toStdWString(); }
+#else
+std::string convert(const QString &s) { return s.toStdString(); }
+#endif
+
+
+QDir get_default_model_directory()
+{
+ return QDir(OPENTRACK_BASE_PATH+ "/" OPENTRACK_LIBRARY_PATH "models");
+}
+
+
+int enum_to_fps(int value)
+{
+ int fps = 0;
+
+ switch (value)
+ {
+ default: eval_once(qDebug() << "neuralnet tracker: invalid fps enum value");
+ [[fallthrough]];
+ case fps_default: fps = 0; break;
+ case fps_30: fps = 30; break;
+ case fps_60: fps = 60; break;
+ case fps_75: fps = 75; break;
+ case fps_125: fps = 125; break;
+ case fps_200: fps = 200; break;
+ case fps_50: fps = 50; break;
+ case fps_100: fps = 100; break;
+ case fps_120: fps = 120; break;
+ case fps_300: fps = 300; break;
+ case fps_250: fps = 250; break;
+ }
+
+ return fps;
+}
+
+
+template<class F>
+struct OnScopeExit
+{
+ explicit OnScopeExit(F&& f) : f_{ f } {}
+ ~OnScopeExit() noexcept
+ {
+ f_();
+ }
+ F f_;
+};
+
+
+CamIntrinsics make_intrinsics(const cv::Mat& img, const Settings& settings)
+{
+ const int w = img.cols, h = img.rows;
+ const double diag_fov = settings.fov * M_PI / 180.;
+ const double fov_w = 2.*atan(tan(diag_fov/2.)/sqrt(1. + h/(double)w * h/(double)w));
+ const double fov_h = 2.*atan(tan(diag_fov/2.)/sqrt(1. + w/(double)h * w/(double)h));
+ const double focal_length_w = 1. / tan(.5 * fov_w);
+ const double focal_length_h = 1. / tan(.5 * fov_h);
+ /* a
+ ______ <--- here is sensor area
+ | /
+ | /
+ f | /
+ | / 2 x angle is the fov
+ |/
+ <--- here is the hole of the pinhole camera
+
+ So, a / f = tan(fov / 2)
+ => f = a/tan(fov/2)
+ What is a?
+ 1 if we define f in terms of clip space where the image plane goes from -1 to 1. Because a is the half-width.
+ */
+
+ return {
+ (float)focal_length_w,
+ (float)focal_length_h,
+ (float)fov_w,
+ (float)fov_h
+ };
+}
+
+
+cv::Rect make_crop_rect_multiple_of(const cv::Size &size, int multiple)
+{
+ const int new_w = (size.width / multiple) * multiple;
+ const int new_h = (size.height / multiple) * multiple;
+ return cv::Rect(
+ (size.width-new_w)/2,
+ (size.height-new_h)/2,
+ new_w,
+ new_h
+ );
+}
+
+template<class T>
+cv::Rect_<T> squarize(const cv::Rect_<T> &r)
+{
+ cv::Point_<T> c{r.x + r.width/T(2), r.y + r.height/T(2)};
+ const T sz = std::max(r.height, r.width);
+ return {c.x - sz/T(2), c.y - sz/T(2), sz, sz};
+}
+
+
+template<class T>
+cv::Rect_<T> expand(const cv::Rect_<T>& r, T factor)
+{
+ // xnew = l+.5*w - w*f*0.5 = l + .5*(w - new_w)
+ const cv::Size_<T> new_size = { r.width * factor, r.height * factor };
+ const cv::Point_<T> new_tl = r.tl() + (as_point(r.size()) - as_point(new_size)) / T(2);
+ return cv::Rect_<T>(new_tl, new_size);
+}
+
+
+template<class T>
+cv::Rect_<T> ewa_filter(const cv::Rect_<T>& last, const cv::Rect_<T>& current, T alpha)
+{
+ const auto last_center = T(0.5) * (last.tl() + last.br());
+ const auto cur_center = T(0.5) * (current.tl() + current.br());
+ const cv::Point_<T> new_size = as_point(last.size()) + alpha * (as_point(current.size()) - as_point(last.size()));
+ const cv::Point_<T> new_center = last_center + alpha * (cur_center - last_center);
+ return cv::Rect_<T>(new_center - T(0.5) * new_size, as_size(new_size));
+}
+
+
+cv::Vec3f image_to_world(float x, float y, float size, float reference_size_in_mm, const cv::Size2i& image_size, const CamIntrinsics& intrinsics)
+{
+ /*
+ Compute the location the network outputs in 3d space.
+
+ hhhhhh <- head size (meters)
+ \ | -----------------------
+ \ | \
+ \ | |
+ \ | |- x (meters)
+ ____ <- face.size / width |
+ \ | | |
+ \| |- focal length /
+ ------------------------
+ ------------------------------------------------>> z direction
+ z/x = zi / f
+ zi = image position
+ z = world position
+ f = focal length
+
+ We can also do deltas:
+ dz / x = dzi / f
+ => x = dz / dzi * f
+ which means we can compute x from the head size (dzi) if we assume some reference size (dz).
+ */
+ const float head_size_vertical = 2.f*size; // Size from the model is more like half the real vertical size of a human head.
+ const float xpos = -(intrinsics.focal_length_w * image_size.width * 0.5f) / head_size_vertical * reference_size_in_mm;
+ const float zpos = (x / image_size.width * 2.f - 1.f) * xpos / intrinsics.focal_length_w;
+ const float ypos = (y / image_size.height * 2.f - 1.f) * xpos / intrinsics.focal_length_h;
+ return {xpos, ypos, zpos};
+}
+
+
+vec2 world_to_image(const cv::Vec3f& pos, const cv::Size2i& image_size, const CamIntrinsics& intrinsics)
+{
+ const float xscr = pos[2] / pos[0] * intrinsics.focal_length_w;
+ const float yscr = pos[1] / pos[0] * intrinsics.focal_length_h;
+ const float x = (xscr+1.)*0.5f*image_size.width;
+ const float y = (yscr+1.)*0.5f*image_size.height;
+ return {x, y};
+}
+
+
+cv::Quatf image_to_world(cv::Quatf q)
+{
+ std::swap(q[1], q[3]);
+ q[1] = -q[1];
+ q[2] = -q[2];
+ q[3] = -q[3];
+ return q;
+}
+
+
+cv::Point2f normalize(const cv::Point2f &p, int h, int w)
+{
+ return {
+ p.x/w*2.f-1.f,
+ p.y/h*2.f-1.f
+ };
+}
+
+
+cv::Quatf rotation_from_two_vectors(const vec3 &a, const vec3 &b)
+{
+ // |axis| = |a| * |b| * sin(alpha)
+ const vec3 axis = a.cross(b);
+ // dot = |a|*|b|*cos(alpha)
+ const float dot = a.dot(b);
+ const float len = cv::norm(axis);
+ vec3 normed_axis = axis / len;
+ float angle = std::atan2(len, dot);
+ if (!(std::isfinite(normed_axis[0]) && std::isfinite(normed_axis[1]) && std::isfinite(normed_axis[2])))
+ {
+ angle = 0.f;
+ normed_axis = vec3{1.,0.,0.};
+ }
+ return cv::Quatf::createFromAngleAxis(angle, normed_axis);
+}
+
+
+// Computes correction due to head being off screen center.
+cv::Quatf compute_rotation_correction(const cv::Point3f& p)
+{
+ return rotation_from_two_vectors(
+ {-1.f,0.f,0.f}, p);
+}
+
+
+// Intersection over union. A value between 0 and 1 which measures the match between the bounding boxes.
+template<class T>
+T iou(const cv::Rect_<T> &a, const cv::Rect_<T> &b)
+{
+ auto i = a & b;
+ return double{i.area()} / (a.area()+b.area()-i.area());
+}
+
+
+class GuardedThreadCountSwitch
+{
+ int old_num_threads_cv_ = 1;
+ int old_num_threads_omp_ = 1;
+ public:
+ GuardedThreadCountSwitch(int num_threads)
+ {
+ old_num_threads_cv_ = cv::getNumThreads();
+ old_num_threads_omp_ = omp_get_num_threads();
+ omp_set_num_threads(num_threads);
+ cv::setNumThreads(num_threads);
+ }
+
+ ~GuardedThreadCountSwitch()
+ {
+ omp_set_num_threads(old_num_threads_omp_);
+ cv::setNumThreads(old_num_threads_cv_);
+ }
+
+ GuardedThreadCountSwitch(const GuardedThreadCountSwitch&) = delete;
+ GuardedThreadCountSwitch& operator=(const GuardedThreadCountSwitch&) = delete;
+};
+
+
+bool NeuralNetTracker::detect()
+{
+ double inference_time = 0.;
+
+ OnScopeExit update_inference_time{ [&]() {
+
+ QMutexLocker lck{ &stats_mtx_ };
+ inference_time_ = inference_time;
+ } };
+
+ // If there is no past ROI from the localizer or if the match of its output
+ // with the current ROI is too poor we have to run it again. This causes a
+ // latency spike of maybe an additional 50%. But it only occurs when the user
+ // moves his head far enough - or when the tracking ist lost ...
+ if (!last_localizer_roi_ || !last_roi_ ||
+ iou(*last_localizer_roi_,*last_roi_)<0.25)
+ {
+ auto [p, rect] = localizer_->run(grayscale_);
+ inference_time += localizer_->last_inference_time_millis();
+
+ if (last_roi_ && iou(rect,*last_roi_)>=0.25 && p > 0.5)
+ {
+ // The new ROI matches the result from tracking, so the user is
+ // still there and to not disturb recurrent models, we only update
+ // ...
+ last_localizer_roi_ = rect;
+ }
+ else if (p > 0.5 && rect.height > 32 && rect.width > 32)
+ {
+ // Tracking probably got lost since the ROI's don't match, but the
+ // localizer still finds a face, so we use the ROI from the localizer
+ last_localizer_roi_ = rect;
+ last_roi_ = rect;
+ }
+ else
+ {
+ // Tracking lost and no localization result. The user probably can't be seen.
+ last_roi_.reset();
+ last_localizer_roi_.reset();
+ }
+ }
+
+ if (!last_roi_)
+ {
+ // Last iteration the tracker failed to generate a trustworthy
+ // roi and the localizer also cannot find a face.
+ draw_gizmos({}, {});
+ return false;
+ }
+
+ auto face = poseestimator_->run(grayscale_, *last_roi_);
+ inference_time += poseestimator_->last_inference_time_millis();
+
+ if (!face)
+ {
+ last_roi_.reset();
+ draw_gizmos({}, {});
+ return false;
+ }
+
+ cv::Rect2f roi = expand(face->box, (float)settings_.roi_zoom);
+
+ last_roi_ = ewa_filter(*last_roi_, roi, float(settings_.roi_filter_alpha));
+
+ QuatPose pose = compute_filtered_pose(*face);
+ last_pose_ = pose;
+
+ Affine pose_affine = {
+ pose.rot.toRotMat3x3(cv::QUAT_ASSUME_UNIT),
+ pose.pos };
+
+ {
+ QMutexLocker lck(&mtx_);
+ last_pose_affine_ = pose_affine;
+ }
+
+ draw_gizmos(*face, last_pose_affine_);
+
+ return true;
+}
+
+
+void NeuralNetTracker::draw_gizmos(
+ const std::optional<PoseEstimator::Face> &face,
+ const Affine& pose)
+{
+ if (!is_visible_)
+ return;
+
+ preview_.draw_gizmos(
+ face,
+ last_roi_,
+ last_localizer_roi_,
+ world_to_image(pose.t, grayscale_.size(), intrinsics_));
+
+ if (settings_.show_network_input)
+ {
+ cv::Mat netinput = poseestimator_->last_network_input();
+ preview_.overlay_netinput(netinput);
+ }
+}
+
+
+QuatPose NeuralNetTracker::transform_to_world_pose(const cv::Quatf &face_rotation, const cv::Point2f& face_xy, const float face_size) const
+{
+ const vec3 face_world_pos = image_to_world(
+ face_xy.x, face_xy.y, face_size, HEAD_SIZE_MM,
+ grayscale_.size(),
+ intrinsics_);
+
+ const cv::Quatf rot_correction = compute_rotation_correction(
+ face_world_pos);
+
+ cv::Quatf rot = rot_correction * image_to_world(face_rotation);
+
+ // But this is in general not the location of the rotation joint in the neck.
+ // So we need an extra offset. Which we determine by computing
+ // z,y,z-pos = head_joint_loc + R_face * offset
+ const vec3 local_offset = vec3{
+ static_cast<float>(settings_.offset_fwd),
+ static_cast<float>(settings_.offset_up),
+ static_cast<float>(settings_.offset_right)};
+ const vec3 offset = rotate(rot, local_offset);
+ const vec3 pos = face_world_pos + offset;
+
+ return { rot, pos };
+}
+
+
+QuatPose NeuralNetTracker::compute_filtered_pose(const PoseEstimator::Face &face)
+{
+ if (fps_ > 0.001 && last_pose_ && poseestimator_->has_uncertainty())
+ {
+ auto image2world = [this](const cv::Quatf &face_rotation, const cv::Point2f& face_xy, const float face_size) {
+ return this->transform_to_world_pose(face_rotation, face_xy, face_size); };
+
+ return apply_filter(
+ face,
+ *last_pose_,
+ 1./fps_,
+ std::move(image2world),
+ FiltParams{
+ float(settings_.deadzone_hardness),
+ float(settings_.deadzone_size)
+ });
+ }
+ else
+ {
+ return transform_to_world_pose(face.rotation, face.center, face.size);
+ }
+}
+
+
+NeuralNetTracker::NeuralNetTracker()
+{
+ opencv_init();
+ neuralnet_tracker_tests::run();
+}
+
+
+NeuralNetTracker::~NeuralNetTracker()
+{
+ requestInterruption();
+ wait();
+ // fast start/stop causes breakage
+ portable::sleep(1000);
+}
+
+
+module_status NeuralNetTracker::start_tracker(QFrame* videoframe)
+{
+ videoframe->show();
+ video_widget_ = std::make_unique<cv_video_widget>(videoframe);
+ layout_ = std::make_unique<QHBoxLayout>();
+ layout_->setContentsMargins(0, 0, 0, 0);
+ layout_->addWidget(&*video_widget_);
+ videoframe->setLayout(&*layout_);
+ video_widget_->show();
+ num_threads_ = settings_.num_threads;
+ start();
+ return status_ok();
+}
+
+
+bool NeuralNetTracker::load_and_initialize_model()
+{
+ const QString localizer_model_path_enc =
+ OPENTRACK_BASE_PATH+"/" OPENTRACK_LIBRARY_PATH "/models/head-localizer.onnx";
+ const QString poseestimator_model_path_enc = get_posenet_filename();
+
+ try
+ {
+ env_ = Ort::Env{
+ OrtLoggingLevel::ORT_LOGGING_LEVEL_ERROR,
+ "tracker-neuralnet"
+ };
+ auto opts = Ort::SessionOptions{};
+ // Do thread settings here do anything?
+ // There is a warning which says to control number of threads via
+ // openmp settings. Which is what we do.
+ opts.SetIntraOpNumThreads(num_threads_);
+ opts.SetInterOpNumThreads(1);
+ allocator_info_ = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
+
+ localizer_.emplace(
+ allocator_info_,
+ Ort::Session{env_, convert(localizer_model_path_enc).c_str(), opts});
+
+ qDebug() << "Loading pose net " << poseestimator_model_path_enc;
+ poseestimator_.emplace(
+ allocator_info_,
+ Ort::Session{env_, convert(poseestimator_model_path_enc).c_str(), opts});
+ }
+ catch (const Ort::Exception &e)
+ {
+ qDebug() << "Failed to initialize the neural network models. ONNX error message: "
+ << e.what();
+ return false;
+ }
+ catch (const std::exception &e)
+ {
+ qDebug() << "Failed to initialize the neural network models. Error message: " << e.what();
+ return false;
+ }
+
+ return true;
+}
+
+
+bool NeuralNetTracker::open_camera()
+{
+ int rint = std::clamp(*settings_.resolution, 0, (int)std::size(resolution_choices)-1);
+ resolution_tuple res = resolution_choices[rint];
+ int fps = enum_to_fps(settings_.force_fps);
+
+ QMutexLocker l(&camera_mtx_);
+
+ camera_ = video::make_camera(settings_.camera_name);
+
+ if (!camera_)
+ return false;
+
+ video::impl::camera::info args {};
+
+ if (res.width)
+ {
+ args.width = res.width;
+ args.height = res.height;
+ }
+ if (fps)
+ args.fps = fps;
+
+ args.use_mjpeg = settings_.use_mjpeg;
+
+ if (!camera_->start(args))
+ {
+ qDebug() << "neuralnet tracker: can't open camera";
+ return false;
+ }
+
+ return true;
+}
+
+
+void NeuralNetTracker::run()
+{
+ preview_.init(*video_widget_);
+
+ GuardedThreadCountSwitch switch_num_threads_to(num_threads_);
+
+ if (!open_camera())
+ return;
+
+ if (!load_and_initialize_model())
+ return;
+
+ std::chrono::high_resolution_clock clk;
+
+ while (!isInterruptionRequested())
+ {
+ is_visible_ = check_is_visible();
+ auto t = clk.now();
+ {
+ QMutexLocker l(&camera_mtx_);
+
+ auto [ img, res ] = camera_->get_frame();
+
+ if (!res)
+ {
+ l.unlock();
+ portable::sleep(100);
+ continue;
+ }
+
+ {
+ QMutexLocker lck{&stats_mtx_};
+ resolution_ = { img.width, img.height };
+ }
+
+ auto color = prepare_input_image(img);
+
+ if (is_visible_)
+ preview_.copy_video_frame(color);
+
+ switch (img.channels)
+ {
+ case 1:
+ grayscale_.create(img.height, img.width, CV_8UC1);
+ color.copyTo(grayscale_);
+ break;
+ case 3:
+ cv::cvtColor(color, grayscale_, cv::COLOR_BGR2GRAY);
+ break;
+ default:
+ qDebug() << "Can't handle" << img.channels << "color channels";
+ return;
+ }
+ }
+
+ intrinsics_ = make_intrinsics(grayscale_, settings_);
+
+ detect();
+
+ if (is_visible_)
+ preview_.copy_to_widget(*video_widget_);
+
+ update_fps(
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ clk.now() - t).count()*1.e-3);
+ }
+}
+
+
+cv::Mat NeuralNetTracker::prepare_input_image(const video::frame& frame)
+{
+ auto img = cv::Mat(frame.height, frame.width, CV_8UC(frame.channels), (void*)frame.data, frame.stride);
+
+ // Crop if aspect ratio is not 4:3
+ if (img.rows*4 != img.cols*3)
+ {
+ img = img(make_crop_rect_for_aspect(img.size(), 4, 3));
+ }
+
+ img = img(make_crop_rect_multiple_of(img.size(), 4));
+
+ if (img.cols > 640)
+ {
+ cv::pyrDown(img, downsized_original_images_[0]);
+ img = downsized_original_images_[0];
+ }
+ if (img.cols > 640)
+ {
+ cv::pyrDown(img, downsized_original_images_[1]);
+ img = downsized_original_images_[1];
+ }
+
+ return img;
+}
+
+
+void NeuralNetTracker::update_fps(double dt)
+{
+ const double alpha = dt/(dt + RC);
+ if (dt > 1e-6)
+ {
+ QMutexLocker lck{&stats_mtx_};
+ fps_ *= 1 - alpha;
+ fps_ += alpha * 1./dt;
+ }
+}
+
+
+void NeuralNetTracker::data(double *data)
+{
+ Affine tmp = [&]()
+ {
+ QMutexLocker lck(&mtx_);
+ return last_pose_affine_;
+ }();
+
+ const auto& mx = tmp.R.col(0);
+ const auto& my = tmp.R.col(1);
+ const auto& mz = tmp.R.col(2);
+
+ // For reference: https://en.wikipedia.org/wiki/Euler_angles. Section "Rotation matrix". The relevant matrix is
+ // under "Tait-Bryan angles", row with "Y_alpha Z_beta X_gamma = ...".
+ // Because for the NN tracker x is forward, and y is up. We can see that the x axis is independent of roll. Thus it
+ // is relatively easy to figure out the yaw and pitch angles (alpha and beta).
+ const float yaw = std::atan2(mx(2), mx(0));
+ const float pitch = -std::atan2(-mx(1), std::sqrt(mx(2)*mx(2)+mx(0)*mx(0)));
+ // For the roll angle we recognize that the matrix entries in the second row contain cos(pitch)*cos(roll), and
+ // cos(pitch)*sin(roll). Using atan2 eliminates the common pitch factor and we obtain the roll angle.
+ const float roll = std::atan2(-mz(1), my(1));
+ {
+ constexpr double rad2deg = 180/M_PI;
+ data[Yaw] = rad2deg * yaw;
+ data[Pitch] = rad2deg * pitch;
+ data[Roll] = -rad2deg * roll;
+
+ // convert to cm
+ data[TX] = -tmp.t[2] * 0.1;
+ data[TY] = tmp.t[1] * 0.1;
+ data[TZ] = -tmp.t[0] * 0.1;
+ }
+}
+
+
+Affine NeuralNetTracker::pose()
+{
+ QMutexLocker lck(&mtx_);
+ return last_pose_affine_;
+}
+
+
+std::tuple<cv::Size,double, double> NeuralNetTracker::stats() const
+{
+ QMutexLocker lck(&stats_mtx_);
+ return { resolution_, fps_, inference_time_ };
+}
+
+
+QString NeuralNetTracker::get_posenet_filename() const
+{
+ QString filename = settings_.posenet_file;
+ if (QFileInfo(filename).isRelative())
+ filename = get_default_model_directory().absoluteFilePath(filename);
+ return filename;
+}
+
+
+void NeuralNetDialog::make_fps_combobox()
+{
+ for (int k = 0; k < fps_MAX; k++)
+ {
+ const int hz = enum_to_fps(k);
+ const QString name = (hz == 0) ? tr("Default") : QString::number(hz);
+ ui_.cameraFPS->addItem(name, k);
+ }
+}
+
+void NeuralNetDialog::make_resolution_combobox()
+{
+ int k=0;
+ for (const auto [w, h] : resolution_choices)
+ {
+ const QString s = (w == 0)
+ ? tr("Default")
+ : QString::number(w) + " x " + QString::number(h);
+ ui_.resolution->addItem(s, k++);
+ }
+}
+
+
+NeuralNetDialog::NeuralNetDialog() :
+ trans_calib_(1, 2)
+{
+ ui_.setupUi(this);
+
+ make_fps_combobox();
+ make_resolution_combobox();
+
+ for (const auto& str : video::camera_names())
+ ui_.cameraName->addItem(str);
+
+ tie_setting(settings_.camera_name, ui_.cameraName);
+ tie_setting(settings_.fov, ui_.cameraFOV);
+ tie_setting(settings_.offset_fwd, ui_.tx_spin);
+ tie_setting(settings_.offset_up, ui_.ty_spin);
+ tie_setting(settings_.offset_right, ui_.tz_spin);
+ tie_setting(settings_.show_network_input, ui_.showNetworkInput);
+ tie_setting(settings_.roi_filter_alpha, ui_.roiFilterAlpha);
+ tie_setting(settings_.use_mjpeg, ui_.use_mjpeg);
+ tie_setting(settings_.roi_zoom, ui_.roiZoom);
+ tie_setting(settings_.num_threads, ui_.threadCount);
+ tie_setting(settings_.resolution, ui_.resolution);
+ tie_setting(settings_.force_fps, ui_.cameraFPS);
+ tie_setting(settings_.posenet_file, ui_.posenetFileDisplay);
+
+ connect(ui_.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
+ connect(ui_.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
+ connect(ui_.camera_settings, SIGNAL(clicked()), this, SLOT(camera_settings()));
+ connect(ui_.posenetSelectButton, SIGNAL(clicked()), this, SLOT(onSelectPoseNetFile()));
+ connect(&settings_.camera_name, value_::value_changed<QString>(), this, &NeuralNetDialog::update_camera_settings_state);
+
+ update_camera_settings_state(settings_.camera_name);
+
+ connect(&calib_timer_, &QTimer::timeout, this, &NeuralNetDialog::trans_calib_step);
+ calib_timer_.setInterval(35);
+ connect(ui_.tcalib_button,SIGNAL(toggled(bool)), this, SLOT(startstop_trans_calib(bool)));
+
+ connect(&tracker_status_poll_timer_, &QTimer::timeout, this, &NeuralNetDialog::status_poll);
+ tracker_status_poll_timer_.setInterval(250);
+ tracker_status_poll_timer_.start();
+}
+
+void NeuralNetDialog::save()
+{
+ settings_.b->save();
+}
+
+void NeuralNetDialog::reload()
+{
+ settings_.b->reload();
+}
+
+void NeuralNetDialog::doOK()
+{
+ save();
+ close();
+}
+
+
+void NeuralNetDialog::doCancel()
+{
+ close();
+}
+
+
+void NeuralNetDialog::camera_settings()
+{
+ if (tracker_)
+ {
+ QMutexLocker l(&tracker_->camera_mtx_);
+ (void)tracker_->camera_->show_dialog();
+ }
+ else
+ (void)video::show_dialog(settings_.camera_name);
+}
+
+
+void NeuralNetDialog::update_camera_settings_state(const QString& name)
+{
+ (void)name;
+ ui_.camera_settings->setEnabled(true);
+}
+
+
+void NeuralNetDialog::register_tracker(ITracker * x)
+{
+ tracker_ = static_cast<NeuralNetTracker*>(x);
+ ui_.tcalib_button->setEnabled(true);
+}
+
+
+void NeuralNetDialog::unregister_tracker()
+{
+ tracker_ = nullptr;
+ ui_.tcalib_button->setEnabled(false);
+}
+
+bool NeuralNetDialog::embeddable() noexcept
+{
+ return true;
+}
+
+void NeuralNetDialog::set_buttons_visible(bool x)
+{
+ ui_.buttonBox->setVisible(x);
+}
+
+void NeuralNetDialog::status_poll()
+{
+ QString status;
+ if (!tracker_)
+ {
+ status = tr("Tracker Offline");
+ }
+ else
+ {
+ auto [ res, fps, inference_time ] = tracker_->stats();
+ status = tr("%1x%2 @ %3 FPS / Inference: %4 ms").arg(res.width).arg(res.height).arg(int(fps)).arg(inference_time, 0, 'f', 1);
+ }
+ ui_.resolution_display->setText(status);
+}
+
+
+void NeuralNetDialog::trans_calib_step()
+{
+ if (tracker_)
+ {
+ const Affine X_CM = [&]() {
+ QMutexLocker l(&calibrator_mutex_);
+ return tracker_->pose();
+ }();
+ trans_calib_.update(X_CM.R, X_CM.t);
+ auto [_, nsamples] = trans_calib_.get_estimate();
+
+ constexpr int min_yaw_samples = 15;
+ constexpr int min_pitch_samples = 12;
+ constexpr int min_samples = min_yaw_samples+min_pitch_samples;
+
+ // Don't bother counting roll samples. Roll calibration is hard enough
+ // that it's a hidden unsupported feature anyway.
+
+ QString sample_feedback;
+ if (nsamples[0] < min_yaw_samples)
+ sample_feedback = tr("%1 yaw samples. Yaw more to %2 samples for stable calibration.").arg(nsamples[0]).arg(min_yaw_samples);
+ else if (nsamples[1] < min_pitch_samples)
+ sample_feedback = tr("%1 pitch samples. Pitch more to %2 samples for stable calibration.").arg(nsamples[1]).arg(min_pitch_samples);
+ else
+ {
+ const int nsamples_total = nsamples[0] + nsamples[1];
+ sample_feedback = tr("%1 samples. Over %2, good!").arg(nsamples_total).arg(min_samples);
+ }
+ ui_.sample_count_display->setText(sample_feedback);
+ }
+ else
+ startstop_trans_calib(false);
+}
+
+
+void NeuralNetDialog::startstop_trans_calib(bool start)
+{
+ QMutexLocker l(&calibrator_mutex_);
+ // FIXME: does not work ...
+ if (start)
+ {
+ qDebug() << "pt: starting translation calibration";
+ calib_timer_.start();
+ trans_calib_.reset();
+ ui_.sample_count_display->setText(QString());
+ // Tracker must run with zero'ed offset for calibration.
+ settings_.offset_fwd = 0;
+ settings_.offset_up = 0;
+ settings_.offset_right = 0;
+ }
+ else
+ {
+ calib_timer_.stop();
+ qDebug() << "pt: stopping translation calibration";
+ {
+ auto [tmp, nsamples] = trans_calib_.get_estimate();
+ settings_.offset_fwd = int(tmp[0]);
+ settings_.offset_up = int(tmp[1]);
+ settings_.offset_right = int(tmp[2]);
+ }
+ }
+ ui_.tx_spin->setEnabled(!start);
+ ui_.ty_spin->setEnabled(!start);
+ ui_.tz_spin->setEnabled(!start);
+
+ if (start)
+ ui_.tcalib_button->setText(tr("Stop calibration"));
+ else
+ ui_.tcalib_button->setText(tr("Start calibration"));
+}
+
+
+void NeuralNetDialog::onSelectPoseNetFile()
+{
+ const auto root = get_default_model_directory();
+ // Start with the current setting
+ QString filename = settings_.posenet_file;
+ // If the filename is relative then assume that the file is located under the
+ // model directory. Under regular use this should always be the case.
+ if (QFileInfo(filename).isRelative())
+ filename = root.absoluteFilePath(filename);
+ filename = QFileDialog::getOpenFileName(this,
+ tr("Select Pose Net ONNX"), filename, tr("ONNX Files (*.onnx)"));
+ // In case the user aborted.
+ if (filename.isEmpty())
+ return;
+ // When a file under the model directory was selected we can get rid of the
+ // directory prefix. This is more robust than storing absolute paths, e.g.
+ // in case the user moves the opentrack install folder / reuses old settings.
+ // When the file is not in the model directory, we have to use the absolute path,
+ // which is also fine as developer feature.
+ if (filename.startsWith(root.absolutePath()))
+ filename = root.relativeFilePath(filename);
+ settings_.posenet_file = filename;
+}
+
+
+Settings::Settings() : opts("neuralnet-tracker") {}
+
+} // neuralnet_tracker_ns
+
+OPENTRACK_DECLARE_TRACKER(NeuralNetTracker, NeuralNetDialog, NeuralNetMetadata)
diff --git a/tracker-neuralnet/ftnoir_tracker_neuralnet.h b/tracker-neuralnet/ftnoir_tracker_neuralnet.h
new file mode 100644
index 00000000..fe755f51
--- /dev/null
+++ b/tracker-neuralnet/ftnoir_tracker_neuralnet.h
@@ -0,0 +1,230 @@
+/* Copyright (c) 2021 Michael Welter <michael@welter-4d.de>
+ *
+ * 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 "ui_neuralnet-trackercontrols.h"
+#include "model_adapters.h"
+#include "deadzone_filter.h"
+#include "preview.h"
+
+#include "options/options.hpp"
+#include "api/plugin-api.hpp"
+#include "cv/video-widget.hpp"
+#include "cv/translation-calibrator.hpp"
+#include "cv/numeric.hpp"
+#include "compat/timer.hpp"
+#include "video/camera.hpp"
+#include "cv/affine.hpp"
+
+#include <QObject>
+#include <QThread>
+#include <QMutex>
+#include <QHBoxLayout>
+#include <QDialog>
+#include <QTimer>
+
+#include <memory>
+#include <cinttypes>
+#include <array>
+
+#include <opencv2/core.hpp>
+#include <opencv2/imgproc.hpp>
+
+
+namespace neuralnet_tracker_ns
+{
+
+
+using namespace options;
+
+
+enum fps_choices
+{
+ fps_default = 0,
+ fps_30 = 1,
+ fps_60 = 2,
+ fps_75 = 3,
+ fps_125 = 4,
+ fps_200 = 5,
+ fps_50 = 6,
+ fps_100 = 7,
+ fps_120 = 8,
+ fps_300 = 9,
+ fps_250 = 10,
+ fps_MAX = 11,
+};
+
+struct resolution_tuple
+{
+ int width;
+ int height;
+};
+
+static const std::array<resolution_tuple, 7> resolution_choices =
+{{
+ { 320, 240 },
+ { 640, 480 },
+ { 800, 600 },
+ { 1024, 768 },
+ { 1280, 720 },
+ { 1920, 1080},
+ { 0, 0 }
+}};
+
+
+struct Settings : opts {
+ value<int> offset_fwd { b, "offset-fwd", 200 }, // Millimeters
+ offset_up { b, "offset-up", 0 },
+ offset_right { b, "offset-right", 0 };
+ value<QString> camera_name { b, "camera-name", ""};
+ value<int> fov { b, "field-of-view", 56 };
+ value<fps_choices> force_fps { b, "force-fps", fps_default };
+ value<bool> show_network_input { b, "show-network-input", false };
+ value<double> roi_filter_alpha{ b, "roi-filter-alpha", 1. };
+ value<double> roi_zoom{ b, "roi-zoom", 1. };
+ value<bool> use_mjpeg { b, "use-mjpeg", false };
+ value<int> num_threads { b, "num-threads", 1 };
+ value<int> resolution { b, "force-resolution", 0 };
+ value<double> deadzone_size { b, "deadzone-size", 1. };
+ value<double> deadzone_hardness { b, "deadzone-hardness", 1.5 };
+ value<QString> posenet_file { b, "posenet-file", "head-pose-0.3-big-quantized.onnx" };
+ Settings();
+};
+
+
+struct CamIntrinsics
+{
+ float focal_length_w;
+ float focal_length_h;
+ float fov_w;
+ float fov_h;
+};
+
+
+class NeuralNetTracker : protected virtual QThread, public ITracker
+{
+ Q_OBJECT
+public:
+ NeuralNetTracker();
+ ~NeuralNetTracker() override;
+ module_status start_tracker(QFrame* frame) override;
+ void data(double *data) override;
+ void run() override;
+ Affine pose();
+ std::tuple<cv::Size, double, double> stats() const;
+
+ QMutex camera_mtx_;
+ std::unique_ptr<video::impl::camera> camera_;
+
+private:
+ bool detect();
+ bool open_camera();
+ void set_intrinsics();
+ cv::Mat prepare_input_image(const video::frame& frame);
+ bool load_and_initialize_model();
+ void draw_gizmos(
+ const std::optional<PoseEstimator::Face> &face,
+ const Affine& pose);
+ void update_fps(double dt);
+ // Secretly applies filtering while computing the pose in 3d space.
+ QuatPose compute_filtered_pose(const PoseEstimator::Face &face);
+ // Compute the pose in 3d space taking the network outputs
+ QuatPose transform_to_world_pose(const cv::Quatf &face_rotation, const cv::Point2f& face_xy, const float face_size) const;
+ QString get_posenet_filename() const;
+
+ Settings settings_;
+ std::optional<Localizer> localizer_;
+ std::optional<PoseEstimator> poseestimator_;
+ Ort::Env env_{nullptr};
+ Ort::MemoryInfo allocator_info_{nullptr};
+
+ CamIntrinsics intrinsics_{};
+ cv::Mat grayscale_;
+ std::array<cv::Mat,2> downsized_original_images_ = {}; // Image pyramid
+ std::optional<cv::Rect2f> last_localizer_roi_;
+ std::optional<cv::Rect2f> last_roi_;
+ static constexpr float HEAD_SIZE_MM = 200.f; // In the vertical. Approximately.
+
+ mutable QMutex stats_mtx_;
+ double fps_ = 0;
+ double inference_time_ = 0;
+ cv::Size resolution_ = {};
+
+ static constexpr double RC = .25;
+ int num_threads_ = 1;
+ bool is_visible_ = true;
+
+ QMutex mtx_ = {}; // Protects the pose
+ std::optional<QuatPose> last_pose_ = {};
+ Affine last_pose_affine_ = {};
+
+ Preview preview_;
+ std::unique_ptr<cv_video_widget> video_widget_;
+ std::unique_ptr<QHBoxLayout> layout_;
+};
+
+
+class NeuralNetDialog : public ITrackerDialog
+{
+ Q_OBJECT
+public:
+ NeuralNetDialog();
+ void register_tracker(ITracker * x) override;
+ void unregister_tracker() override;
+
+ bool embeddable() noexcept override;
+ void set_buttons_visible(bool x) override;
+private:
+ void make_fps_combobox();
+ void make_resolution_combobox();
+
+ Ui::Form ui_;
+ Settings settings_;
+ // Calibration code mostly taken from point tracker
+ QTimer calib_timer_;
+ TranslationCalibrator trans_calib_;
+ QMutex calibrator_mutex_;
+ QTimer tracker_status_poll_timer_;
+ NeuralNetTracker* tracker_ = nullptr;
+
+private Q_SLOTS:
+ void save() override;
+ void reload() override;
+ void doOK();
+ void doCancel();
+ void camera_settings();
+ void update_camera_settings_state(const QString& name);
+ void startstop_trans_calib(bool start);
+ void trans_calib_step();
+ void status_poll();
+ void onSelectPoseNetFile();
+};
+
+
+class NeuralNetMetadata : public Metadata
+{
+ Q_OBJECT
+ QString name() override { return QString("neuralnet tracker"); }
+ QIcon icon() override { return QIcon(":/images/neuralnet.png"); }
+};
+
+
+} // neuralnet_tracker_ns
+
+
+namespace neuralnet_tracker_tests
+{
+
+void run();
+
+}
+
+
+using neuralnet_tracker_ns::NeuralNetTracker;
+using neuralnet_tracker_ns::NeuralNetDialog;
+using neuralnet_tracker_ns::NeuralNetMetadata;
diff --git a/tracker-neuralnet/images/neuralnet.png b/tracker-neuralnet/images/neuralnet.png
new file mode 100644
index 00000000..1a10c53c
--- /dev/null
+++ b/tracker-neuralnet/images/neuralnet.png
Binary files differ
diff --git a/tracker-neuralnet/lang/de_DE.ts b/tracker-neuralnet/lang/de_DE.ts
new file mode 100644
index 00000000..6261eec0
--- /dev/null
+++ b/tracker-neuralnet/lang/de_DE.ts
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>Form</name>
+ <message>
+ <source>Tracker settings</source>
+ <translation>Tracker-Einstellungen</translation>
+ </message>
+ <message>
+ <source>Head Center Offset</source>
+ <translation>Versatz zur Kopfmitte</translation>
+ </message>
+ <message>
+ <source>Right</source>
+ <translation>Rechts</translation>
+ </message>
+ <message>
+ <source>Forward</source>
+ <translation>Vorwärts</translation>
+ </message>
+ <message>
+ <source>Up</source>
+ <translation>Hoch</translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation> mm</translation>
+ </message>
+ <message>
+ <source>Use only yaw and pitch while calibrating.
+Don&apos;t roll or change position.</source>
+ <translation>Während der Kalibrierung nur gieren und nicken.
+Bitte nicht rollen oder die Position ändern.</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation>Kalibrierung starten</translation>
+ </message>
+ <message>
+ <source>Camera Configuration</source>
+ <translation>Kamera-Konfiguration</translation>
+ </message>
+ <message>
+ <source>Diagonal FOV</source>
+ <translation>Diagonales Sichtfeld</translation>
+ </message>
+ <message>
+ <source>Camera name</source>
+ <translation>Kamera-Name</translation>
+ </message>
+ <message>
+ <source>Field of view. Needed to transform the pose to world coordinates.</source>
+ <translation>Sichtfeld. Benötigt, um die Pose in Welt-Koordinaten zu übersetzen.</translation>
+ </message>
+ <message>
+ <source>The requested resolution for cases where the camera delivers maximum frame rate only for a particular resolution. The image may still be downscaled to the internal resolution.</source>
+ <translation>Die angeforderte Auflösung für Fälle, in denen die Kamera die maximale Bildrate nur bei bestimmten Auflösungen ausgibt. Das Bild wird möglicherweise weiterhin herunterskaliert auf die interne Auflösung.</translation>
+ </message>
+ <message>
+ <source>Resolution</source>
+ <translation>Auflösung</translation>
+ </message>
+ <message>
+ <source>Requested video frame rate. Actual setting may not be supported by the camera.</source>
+ <translation>Angeforderte Bildrate. Die tatsächliche Einstellungen wird von der Kamera möglicherweise nicht unterstützt.</translation>
+ </message>
+ <message>
+ <source>Frames per second</source>
+ <translation>Bilder pro Sekunde</translation>
+ </message>
+ <message>
+ <source>MJPEG</source>
+ <translation>MJPEG</translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation>Kamera-Einstellungen</translation>
+ </message>
+ <message>
+ <source>Tuning / Debug</source>
+ <translation>Tuning / Fehlersuche</translation>
+ </message>
+ <message>
+ <source>Thread Count</source>
+ <translation>Anzahl der Threads</translation>
+ </message>
+ <message>
+ <source>Number of threads. Can be used to balance the CPU load between the game and the tracker.</source>
+ <translation>Anzahl der Threads. Kann verwendet werden, um die CPU-Last zwischen Spiel und Tracker zu balancieren.</translation>
+ </message>
+ <message>
+ <source>Show the image patch that the pose estimation model sees.</source>
+ <translation>Zeigt den Bildausschnitt, den das Modell zur Posenabschätzung sieht.</translation>
+ </message>
+ <message>
+ <source>Show Network Input</source>
+ <translation>Zeige Netzwerk-Eingabe</translation>
+ </message>
+ <message>
+ <source>ROI Smoothing Alpha</source>
+ <translation>ROI-Glättungsalpha</translation>
+ </message>
+ <message>
+ <source>Amount of smoothing of the face region coordinates. Can help stabilize the pose.</source>
+ <translation>Umfang der Glättung der Gesichtkoordinaten. Kann helfen, die Pose zu stabilisieren.</translation>
+ </message>
+ <message>
+ <source>ROI Zoom</source>
+ <translation>ROI-Zoom</translation>
+ </message>
+ <message>
+ <source>Zoom factor for the face region. Applied before the patch is fed into the pose estimation model. There is a sweet spot near 1.</source>
+ <translation>Zoom-Faktor der Gesichtsregion. Wird angewendet, bevor der Bildausschnitt zum Posen-Abschätzungsmodell gesendet wird. Der Sweet-Spot liegt nahe bei 1.</translation>
+ </message>
+ <message>
+ <source>Select the pose network. Changes take affect on the next tracker start</source>
+ <translation>Wählt das Pose-Netzwerk. Die Änderungen treten beim nächsten Start des Trackers inkraft</translation>
+ </message>
+ <message>
+ <source>Select Pose Net ONNX</source>
+ <translation>Wähle Pose-Netzwerk ONNX</translation>
+ </message>
+ <message>
+ <source>&lt;the pose net file&gt;</source>
+ <translation>&lt;die pose netzwerk datei&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>neuralnet_tracker_ns::NeuralNetDialog</name>
+ <message>
+ <source>Default</source>
+ <translation>Standard</translation>
+ </message>
+ <message>
+ <source>Tracker Offline</source>
+ <translation>Tracker offline</translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS / Inference: %4 ms</source>
+ <translation>%1x%2 @ %3 FPS / Inferenz: %4 ms</translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation>%1 Gieren-Proben. Weiterhin gieren bis %2 Proben für eine stabile Kalibrierung.</translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation>%1 Nicken-Proben. Weiterhin nicken bis %2 Proben für eine stabile Kalibrierung.</translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation>%1 Proben. Mehr als %2, gut!</translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation>Kalibrierung stoppen</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation>Kalibrierung starten</translation>
+ </message>
+ <message>
+ <source>Select Pose Net ONNX</source>
+ <translation>Wähle Pose-Netzwerk ONNX</translation>
+ </message>
+ <message>
+ <source>ONNX Files (*.onnx)</source>
+ <translation>ONNX-Dateien (*.onnx)</translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-neuralnet/lang/nl_NL.ts b/tracker-neuralnet/lang/nl_NL.ts
new file mode 100644
index 00000000..27da4f5a
--- /dev/null
+++ b/tracker-neuralnet/lang/nl_NL.ts
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>Form</name>
+ <message>
+ <source>Tracker settings</source>
+ <translation>Tracker-instellingen</translation>
+ </message>
+ <message>
+ <source>Frames per second</source>
+ <translation>Frames per seconde</translation>
+ </message>
+ <message>
+ <source>Camera name</source>
+ <translation>Cameranaam</translation>
+ </message>
+ <message>
+ <source>Diagonal FOV</source>
+ <translation>Diagonale FOV</translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation>Camera-instellingen</translation>
+ </message>
+ <message>
+ <source>Camera Configuration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Head Center Offset</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use only yaw and pitch while calibrating.
+Don&apos;t roll or change position.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Right</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Forward</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Up</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Show Network Input</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>MJPEG</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tuning / Debug</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ROI Smoothing Alpha</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ROI Zoom</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Thread Count</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Resolution</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Field of view. Needed to transform the pose to world coordinates.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Requested video frame rate. Actual setting may not be supported by the camera.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The requested resolution for cases where the camera delivers maximum frame rate only for a particular resolution. The image may still be downscaled to the internal resolution.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Number of threads. Can be used to balance the CPU load between the game and the tracker.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Show the image patch that the pose estimation model sees.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Amount of smoothing of the face region coordinates. Can help stabilize the pose.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Zoom factor for the face region. Applied before the patch is fed into the pose estimation model. There is a sweet spot near 1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Pose Net ONNX</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;the pose net file&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select the pose network. Changes take affect on the next tracker start</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>neuralnet_tracker_ns::NeuralNetDialog</name>
+ <message>
+ <source>Default</source>
+ <translation type="unfinished">Standaard</translation>
+ </message>
+ <message>
+ <source>Tracker Offline</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS / Inference: %4 ms</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Pose Net ONNX</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ONNX Files (*.onnx)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-neuralnet/lang/ru_RU.ts b/tracker-neuralnet/lang/ru_RU.ts
new file mode 100644
index 00000000..c32d4fa7
--- /dev/null
+++ b/tracker-neuralnet/lang/ru_RU.ts
@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>Form</name>
+ <message>
+ <source>Tracker settings</source>
+ <translation>ÐаÑтройки трекера</translation>
+ </message>
+ <message>
+ <source>Diagonal FOV</source>
+ <translation>Угол обзора</translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation>ÐаÑтройки камеры</translation>
+ </message>
+ <message>
+ <source>Frames per second</source>
+ <translation>Кадры в Ñекунду</translation>
+ </message>
+ <message>
+ <source>Camera name</source>
+ <translation>Камера</translation>
+ </message>
+ <message>
+ <source>Camera Configuration</source>
+ <translation>ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ ÐºÐ°Ð¼ÐµÑ€Ñ‹</translation>
+ </message>
+ <message>
+ <source>Head Center Offset</source>
+ <translation>Смещение центра головы</translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation> мм</translation>
+ </message>
+ <message>
+ <source>Use only yaw and pitch while calibrating.
+Don&apos;t roll or change position.</source>
+ <translation>Поворачивайте голову влево-вправо и наклонÑйте вверх-вниз.
+Ðе наклонÑйте набок и не Ñмещайте голову в Ñторону.</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation>Ðачать калибровку</translation>
+ </message>
+ <message>
+ <source>Right</source>
+ <translation>Вправо</translation>
+ </message>
+ <message>
+ <source>Forward</source>
+ <translation>Вперед</translation>
+ </message>
+ <message>
+ <source>Up</source>
+ <translation>Вверх</translation>
+ </message>
+ <message>
+ <source>Show Network Input</source>
+ <translation>Показать входные данные</translation>
+ </message>
+ <message>
+ <source>MJPEG</source>
+ <translation>ИÑпользовать MJPEG</translation>
+ </message>
+ <message>
+ <source>Tuning / Debug</source>
+ <translation>Ð¢Ð¾Ð½ÐºÐ°Ñ Ð½Ð°Ñтройка</translation>
+ </message>
+ <message>
+ <source>ROI Smoothing Alpha</source>
+ <translation>Сглаживание ROI</translation>
+ </message>
+ <message>
+ <source>ROI Zoom</source>
+ <translation>МаÑштабирование ROI</translation>
+ </message>
+ <message>
+ <source>Thread Count</source>
+ <translation>КоличеÑтво потоков</translation>
+ </message>
+ <message>
+ <source>Resolution</source>
+ <translation>Разрешение</translation>
+ </message>
+ <message>
+ <source>Field of view. Needed to transform the pose to world coordinates.</source>
+ <translation>Угол обзора камеры. ТребуетÑÑ Ð´Ð»Ñ Ð¿Ñ€ÐµÐ¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð³Ð¾Ð»Ð¾Ð²Ñ‹ в глобальные координаты</translation>
+ </message>
+ <message>
+ <source>Requested video frame rate. Actual setting may not be supported by the camera.</source>
+ <translation>ЧаÑтота кадров. Реальные Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ не поддерживатьÑÑ ÐºÐ°Ð¼ÐµÑ€Ð¾Ð¹.</translation>
+ </message>
+ <message>
+ <source>The requested resolution for cases where the camera delivers maximum frame rate only for a particular resolution. The image may still be downscaled to the internal resolution.</source>
+ <translation>Разрешение камеры, Ð´Ð»Ñ Ñ‚ÐµÑ… Ñлучаев, когда быÑтродейÑтвие камеры макÑимально в определенном разрешении. Может быть маÑштабировано до внутреннего разрешениÑ.</translation>
+ </message>
+ <message>
+ <source>Number of threads. Can be used to balance the CPU load between the game and the tracker.</source>
+ <translation>КоличеÑтво потоков. ИÑпользуетÑÑ Ð´Ð»Ñ Ð±Ð°Ð»Ð°Ð½Ñировки нагрузки на процеÑÑор между игрой и трекером.</translation>
+ </message>
+ <message>
+ <source>Show the image patch that the pose estimation model sees.</source>
+ <translation>Показать изображение, иÑпользуемое моделью Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ð¸</translation>
+ </message>
+ <message>
+ <source>Amount of smoothing of the face region coordinates. Can help stabilize the pose.</source>
+ <translation>Сглаживание координат облаÑти лица. Может помочь Ñтабилизировать позицию.</translation>
+ </message>
+ <message>
+ <source>Zoom factor for the face region. Applied before the patch is fed into the pose estimation model. There is a sweet spot near 1.</source>
+ <translation>Фактор маÑÑˆÑ‚Ð°Ð±Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾Ð±Ð»Ð°Ñти лица. ПрименÑетÑÑ Ð¿ÐµÑ€ÐµÐ´ передачей кадра в модель Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ð¸. Ðаилучшие результаты близки к 1</translation>
+ </message>
+ <message>
+ <source>Select Pose Net ONNX</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;the pose net file&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select the pose network. Changes take affect on the next tracker start</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>neuralnet_tracker_ns::NeuralNetDialog</name>
+ <message>
+ <source>Default</source>
+ <translation>По умолчанию</translation>
+ </message>
+ <message>
+ <source>Tracker Offline</source>
+ <translation>Трекер выключен</translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS / Inference: %4 ms</source>
+ <translation>%1x%2 @ %3 FPS; Ð’Ñ€ÐµÐ¼Ñ Ð¾Ñ†ÐµÐ½ÐºÐ¸: %4 мÑ</translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation>СÑмплов поворота: %1.
+Поворачивайте голову в Ñтороны до %2 ÑÑмплов Ð´Ð»Ñ Ñтабильной калибрации.</translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation>СÑмплов наклона: %1.
+ÐаклонÑйте голову вниз/вверх до %2 ÑÑмплов Ð´Ð»Ñ Ñтабильной калибрации.</translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation>%1 ÑÑмплов. Более %2, доÑтаточно.</translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation>ОÑтановить калибровку</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation>Ðачать калибровку</translation>
+ </message>
+ <message>
+ <source>Select Pose Net ONNX</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ONNX Files (*.onnx)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-neuralnet/lang/stub.ts b/tracker-neuralnet/lang/stub.ts
new file mode 100644
index 00000000..9609f05e
--- /dev/null
+++ b/tracker-neuralnet/lang/stub.ts
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>Form</name>
+ <message>
+ <source>Tracker settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Diagonal FOV</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Frames per second</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera Configuration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Head Center Offset</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use only yaw and pitch while calibrating.
+Don&apos;t roll or change position.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Right</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Forward</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Up</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Show Network Input</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>MJPEG</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tuning / Debug</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ROI Smoothing Alpha</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ROI Zoom</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Thread Count</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Resolution</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Field of view. Needed to transform the pose to world coordinates.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Requested video frame rate. Actual setting may not be supported by the camera.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The requested resolution for cases where the camera delivers maximum frame rate only for a particular resolution. The image may still be downscaled to the internal resolution.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Number of threads. Can be used to balance the CPU load between the game and the tracker.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Show the image patch that the pose estimation model sees.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Amount of smoothing of the face region coordinates. Can help stabilize the pose.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Zoom factor for the face region. Applied before the patch is fed into the pose estimation model. There is a sweet spot near 1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Pose Net ONNX</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;the pose net file&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select the pose network. Changes take affect on the next tracker start</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>neuralnet_tracker_ns::NeuralNetDialog</name>
+ <message>
+ <source>Default</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker Offline</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS / Inference: %4 ms</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Pose Net ONNX</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ONNX Files (*.onnx)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-neuralnet/lang/zh_CN.ts b/tracker-neuralnet/lang/zh_CN.ts
new file mode 100644
index 00000000..53da04ae
--- /dev/null
+++ b/tracker-neuralnet/lang/zh_CN.ts
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>Form</name>
+ <message>
+ <source>Tracker settings</source>
+ <translation>追踪器设置</translation>
+ </message>
+ <message>
+ <source>Diagonal FOV</source>
+ <translation>对角FOV</translation>
+ </message>
+ <message>
+ <source>Camera name</source>
+ <translation>相机å</translation>
+ </message>
+ <message>
+ <source>Frames per second</source>
+ <translation>FPS</translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation>相机设置</translation>
+ </message>
+ <message>
+ <source>Camera Configuration</source>
+ <translation>相机é…ç½®</translation>
+ </message>
+ <message>
+ <source>Head Center Offset</source>
+ <translation>头部归中补å¿</translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use only yaw and pitch while calibrating.
+Don&apos;t roll or change position.</source>
+ <translation>在校准时åªä½¿ç”¨å航和俯仰,
+ä¸è¦æ»šè½¬æˆ–是改å˜ä½ç½®. </translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation>开始校准</translation>
+ </message>
+ <message>
+ <source>Right</source>
+ <translation>å‘å³</translation>
+ </message>
+ <message>
+ <source>Forward</source>
+ <translation>å‘å‰</translation>
+ </message>
+ <message>
+ <source>Up</source>
+ <translation>å‘上</translation>
+ </message>
+ <message>
+ <source>Show Network Input</source>
+ <translation>展示神ç»ç½‘络输入</translation>
+ </message>
+ <message>
+ <source>MJPEG</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tuning / Debug</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ROI Smoothing Alpha</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ROI Zoom</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Thread Count</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Resolution</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Field of view. Needed to transform the pose to world coordinates.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Requested video frame rate. Actual setting may not be supported by the camera.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The requested resolution for cases where the camera delivers maximum frame rate only for a particular resolution. The image may still be downscaled to the internal resolution.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Number of threads. Can be used to balance the CPU load between the game and the tracker.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Show the image patch that the pose estimation model sees.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Amount of smoothing of the face region coordinates. Can help stabilize the pose.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Zoom factor for the face region. Applied before the patch is fed into the pose estimation model. There is a sweet spot near 1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select the pose network. Changes take affect on the next tracker start</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Select Pose Net ONNX</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;the pose net file&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>neuralnet_tracker_ns::NeuralNetDialog</name>
+ <message>
+ <source>Default</source>
+ <translation>默认</translation>
+ </message>
+ <message>
+ <source>Tracker Offline</source>
+ <translation>追踪器离线</translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS / Inference: %4 ms</source>
+ <translation>%1x%2 @ %3 FPS / 推ç†: %4 ms</translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation>ç»“æŸæ ¡å‡†</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation>开始校准</translation>
+ </message>
+ <message>
+ <source>Select Pose Net ONNX</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>ONNX Files (*.onnx)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-neuralnet/model_adapters.cpp b/tracker-neuralnet/model_adapters.cpp
new file mode 100644
index 00000000..f53478af
--- /dev/null
+++ b/tracker-neuralnet/model_adapters.cpp
@@ -0,0 +1,416 @@
+#include "model_adapters.h"
+
+#include "compat/timer.hpp"
+
+#include <opencv2/core.hpp>
+#include <opencv2/core/quaternion.hpp>
+#include <opencv2/imgproc.hpp>
+
+#include <QDebug>
+
+namespace neuralnet_tracker_ns
+{
+
+
+float sigmoid(float x)
+{
+ return 1.f/(1.f + std::exp(-x));
+}
+
+
+// Defined in ftnoir_tracker_neuralnet.cpp
+// Normally we wouldn't need it here. However ... see below.
+cv::Quatf image_to_world(cv::Quatf q);
+
+
+cv::Quatf world_to_image(cv::Quatf q)
+{
+ // It's its own inverse.
+ return image_to_world(q);
+}
+
+
+cv::Rect2f unnormalize(const cv::Rect2f &r, int h, int w)
+{
+ auto unnorm = [](float x) -> float { return 0.5*(x+1); };
+ auto tl = r.tl();
+ auto br = r.br();
+ auto x0 = unnorm(tl.x)*w;
+ auto y0 = unnorm(tl.y)*h;
+ auto x1 = unnorm(br.x)*w;
+ auto y1 = unnorm(br.y)*h;
+ return {
+ x0, y0, x1-x0, y1-y0
+ };
+}
+
+
+// Returns width and height of the input tensor, or throws.
+// Expects the model to take one tensor as input that must
+// have the shape B x C x H x W, where B=C=1.
+cv::Size get_input_image_shape(const Ort::Session &session)
+{
+ if (session.GetInputCount() < 1)
+ throw std::invalid_argument("Model must take at least one input tensor");
+ const std::vector<std::int64_t> shape =
+ session.GetInputTypeInfo(0).GetTensorTypeAndShapeInfo().GetShape();
+ if (shape.size() != 4)
+ throw std::invalid_argument("Model takes the input tensor in the wrong shape");
+ return { static_cast<int>(shape[3]), static_cast<int>(shape[2]) };
+}
+
+
+Ort::Value create_tensor(const Ort::TypeInfo& info, Ort::Allocator& alloc)
+{
+ const auto shape = info.GetTensorTypeAndShapeInfo().GetShape();
+ auto t = Ort::Value::CreateTensor<float>(
+ alloc, shape.data(), shape.size());
+ memset(t.GetTensorMutableData<float>(), 0, sizeof(float)*info.GetTensorTypeAndShapeInfo().GetElementCount());
+ return t;
+}
+
+
+int find_input_intensity_quantile(const cv::Mat& frame, float percentage)
+{
+ const int channels[] = { 0 };
+ const int hist_size[] = { 256 };
+ float range[] = { 0, 256 };
+ const float* ranges[] = { range };
+ cv::Mat hist;
+ cv::calcHist(&frame, 1, channels, cv::Mat(), hist, 1, hist_size, ranges, true, false);
+ int gray_level = 0;
+ const int num_pixels_quantile = frame.total()*percentage*0.01f;
+ int num_pixels_accum = 0;
+ for (int i=0; i<hist_size[0]; ++i)
+ {
+ num_pixels_accum += hist.at<float>(i);
+ if (num_pixels_accum > num_pixels_quantile)
+ {
+ gray_level = i;
+ break;
+ }
+ }
+ return gray_level;
+}
+
+
+// Automatic brightness adjustment. Scales brightness to lie between -.5 and 0.5, roughly.
+void normalize_brightness(const cv::Mat& frame, cv::Mat& out)
+{
+ const float pct = 90;
+
+ const int brightness = find_input_intensity_quantile(frame, pct);
+
+ const double alpha = brightness<127 ? (pct/100.f*0.5f/std::max(5,brightness)) : 1./255;
+ const double beta = -0.5;
+
+ frame.convertTo(out, CV_32F, alpha, beta);
+}
+
+
+
+Localizer::Localizer(Ort::MemoryInfo &allocator_info, Ort::Session &&session) :
+ session_{std::move(session)},
+ scaled_frame_(INPUT_IMG_HEIGHT, INPUT_IMG_WIDTH, CV_8U),
+ input_mat_(INPUT_IMG_HEIGHT, INPUT_IMG_WIDTH, CV_32F)
+{
+ // Only works when input_mat does not reallocated memory ...which it should not.
+ // Non-owning memory reference to input_mat?
+ // Note: shape = (bach x channels x h x w)
+ const std::int64_t input_shape[4] = { 1, 1, INPUT_IMG_HEIGHT, INPUT_IMG_WIDTH };
+ input_val_ = Ort::Value::CreateTensor<float>(allocator_info, input_mat_.ptr<float>(0), input_mat_.total(), input_shape, 4);
+
+ const std::int64_t output_shape[2] = { 1, 5 };
+ output_val_ = Ort::Value::CreateTensor<float>(allocator_info, results_.data(), results_.size(), output_shape, 2);
+}
+
+
+std::pair<float, cv::Rect2f> Localizer::run(
+ const cv::Mat &frame)
+{
+ auto p = input_mat_.ptr(0);
+
+ cv::resize(frame, scaled_frame_, { INPUT_IMG_WIDTH, INPUT_IMG_HEIGHT }, 0, 0, cv::INTER_AREA);
+ scaled_frame_.convertTo(input_mat_, CV_32F, 1./255., -0.5);
+
+ assert (input_mat_.ptr(0) == p);
+ assert (!input_mat_.empty() && input_mat_.isContinuous());
+ assert (input_mat_.cols == INPUT_IMG_WIDTH && input_mat_.rows == INPUT_IMG_HEIGHT);
+
+ const char* input_names[] = {"x"};
+ const char* output_names[] = {"logit_box"};
+
+ Timer t; t.start();
+
+ session_.Run(Ort::RunOptions{nullptr}, input_names, &input_val_, 1, output_names, &output_val_, 1);
+
+ last_inference_time_ = t.elapsed_ms();
+
+ const cv::Rect2f roi = unnormalize(cv::Rect2f{
+ results_[1],
+ results_[2],
+ results_[3]-results_[1], // Width
+ results_[4]-results_[2] // Height
+ }, frame.rows, frame.cols);
+ const float score = sigmoid(results_[0]);
+
+ return { score, roi };
+}
+
+
+double Localizer::last_inference_time_millis() const
+{
+ return last_inference_time_;
+}
+
+
+std::string PoseEstimator::get_network_input_name(size_t i) const
+{
+#if ORT_API_VERSION >= 12
+ return std::string(&*session_.GetInputNameAllocated(i, allocator_));
+#else
+ return std::string(session_.GetInputName(i, allocator_));
+#endif
+}
+
+std::string PoseEstimator::get_network_output_name(size_t i) const
+{
+#if ORT_API_VERSION >= 12
+ return std::string(&*session_.GetOutputNameAllocated(i, allocator_));
+#else
+ return std::string(session_.GetOutputName(i, allocator_));
+#endif
+}
+
+PoseEstimator::PoseEstimator(Ort::MemoryInfo &allocator_info, Ort::Session &&session)
+ : model_version_{session.GetModelMetadata().GetVersion()}
+ , session_{std::move(session)}
+ , allocator_{session_, allocator_info}
+{
+ using namespace std::literals::string_literals;
+
+ if (session_.GetOutputCount() < 2)
+ throw std::runtime_error("Invalid Model: must have at least two outputs");
+
+ // WARNING: Messy model compatibility issues!
+ // When reading the initial model release, it did not have the version field set.
+ // Reading it here will result in some unspecified value. It's probably UB due to
+ // reading uninitialized memory. But there is little choice.
+ // Now, detection of this old version is messy ... we have to guess based on the
+ // number we get. Getting an uninitialized value matching a valid version is unlikely.
+ // But the real problem is that this line must be updated whenever we want to bump the
+ // version number!!
+ if (model_version_ <= 0 || model_version_ > 4)
+ model_version_ = 1;
+
+ const cv::Size input_image_shape = get_input_image_shape(session_);
+
+ scaled_frame_ = cv::Mat(input_image_shape, CV_8U, cv::Scalar(0));
+ input_mat_ = cv::Mat(input_image_shape, CV_32F, cv::Scalar(0.f));
+
+ {
+ const std::int64_t input_shape[4] = { 1, 1, input_image_shape.height, input_image_shape.width };
+ input_val_.push_back(
+ Ort::Value::CreateTensor<float>(allocator_info, input_mat_.ptr<float>(0), input_mat_.total(), input_shape, 4));
+ }
+
+ struct TensorSpec
+ {
+ std::vector<int64_t> shape;
+ float* buffer = nullptr;
+ size_t element_count = 0;
+ bool available = false;
+ };
+
+ std::unordered_map<std::string, TensorSpec> understood_outputs = {
+ { "pos_size", TensorSpec{ { 1, 3 }, &output_coord_[0], output_coord_.rows } },
+ { "quat", TensorSpec{ { 1, 4}, &output_quat_[0], output_quat_.rows } },
+ { "box", TensorSpec{ { 1, 4}, &output_box_[0], output_box_.rows } },
+ { "rotaxis_scales_tril", TensorSpec{ {1, 3, 3}, output_rotaxis_scales_tril_.val, 9 }},
+ { "rotaxis_std", TensorSpec{ {1, 3, 3}, output_rotaxis_scales_tril_.val, 9 }}, // TODO: Delete when old models aren't used any more
+ { "pos_size_std", TensorSpec{ {1, 3}, output_coord_scales_std_.val, output_coord_scales_std_.rows}},
+ { "pos_size_scales", TensorSpec{ {1, 3}, output_coord_scales_std_.val, output_coord_scales_std_.rows}},
+ { "pos_size_scales_tril", TensorSpec{ {1, 3, 3}, output_coord_scales_tril_.val, 9}}
+ };
+
+ qDebug() << "Pose model inputs (" << session_.GetInputCount() << ")";
+ qDebug() << "Pose model outputs (" << session_.GetOutputCount() << "):";
+ output_names_.resize(session_.GetOutputCount());
+ output_c_names_.resize(session_.GetOutputCount());
+ for (size_t i=0; i<session_.GetOutputCount(); ++i)
+ {
+ const std::string name = get_network_output_name(i);
+ const auto& output_info = session_.GetOutputTypeInfo(i);
+ const auto& onnx_tensor_spec = output_info.GetTensorTypeAndShapeInfo();
+ auto my_tensor_spec_it = understood_outputs.find(name);
+
+ qDebug() << "\t" << name.c_str() << " (" << onnx_tensor_spec.GetShape() << ") dtype: " << onnx_tensor_spec.GetElementType() << " " <<
+ (my_tensor_spec_it != understood_outputs.end() ? "ok" : "unknown");
+
+ if (my_tensor_spec_it != understood_outputs.end())
+ {
+ TensorSpec& t = my_tensor_spec_it->second;
+ if (onnx_tensor_spec.GetShape() != t.shape ||
+ onnx_tensor_spec.GetElementType() != Ort::TypeToTensorType<float>::type)
+ throw std::runtime_error("Invalid output tensor spec for "s + name);
+ output_val_.push_back(Ort::Value::CreateTensor<float>(
+ allocator_info, t.buffer, t.element_count, t.shape.data(), t.shape.size()));
+ t.available = true;
+ }
+ else
+ {
+ // Create tensor regardless and ignore output
+ output_val_.push_back(create_tensor(output_info, allocator_));
+ }
+ output_names_[i] = name;
+ output_c_names_[i] = output_names_[i].c_str();
+ }
+
+ has_uncertainty_ = understood_outputs.at("rotaxis_scales_tril").available ||
+ understood_outputs.at("rotaxis_std").available;
+ has_uncertainty_ &= understood_outputs.at("pos_size_std").available ||
+ understood_outputs.at("pos_size_scales").available ||
+ understood_outputs.at("pos_size_scales_tril").available;
+ pos_scale_uncertainty_is_matrix_ = understood_outputs.at("pos_size_scales_tril").available;
+
+ input_names_.resize(session_.GetInputCount());
+ input_c_names_.resize(session_.GetInputCount());
+ for (size_t i = 0; i < session_.GetInputCount(); ++i)
+ {
+ input_names_[i] = get_network_input_name(i);
+ input_c_names_[i] = input_names_[i].c_str();
+ }
+
+ assert (input_names_.size() == input_val_.size());
+ assert (output_names_.size() == output_val_.size());
+}
+
+
+std::optional<PoseEstimator::Face> PoseEstimator::run(
+ const cv::Mat &frame, const cv::Rect &box)
+{
+ cv::Mat cropped;
+
+ const int patch_size = std::max(box.width, box.height)*1.05;
+ const cv::Point2f patch_center = {
+ std::clamp<float>(box.x + 0.5f*box.width, 0.f, frame.cols),
+ std::clamp<float>(box.y + 0.5f*box.height, 0.f, frame.rows)
+ };
+ cv::getRectSubPix(frame, {patch_size, patch_size}, patch_center, cropped);
+
+ // Will get failure if patch_center is outside image boundaries settings.
+ // Have to catch this case.
+ if (cropped.rows != patch_size || cropped.cols != patch_size)
+ return {};
+
+ [[maybe_unused]] auto* p = input_mat_.ptr(0);
+
+ cv::resize(cropped, scaled_frame_, scaled_frame_.size(), 0, 0, cv::INTER_AREA);
+
+ normalize_brightness(scaled_frame_, input_mat_);
+
+ assert (input_mat_.ptr(0) == p);
+ assert (!input_mat_.empty() && input_mat_.isContinuous());
+
+ Timer t; t.start();
+
+ try
+ {
+ session_.Run(
+ Ort::RunOptions{ nullptr },
+ input_c_names_.data(),
+ input_val_.data(),
+ input_val_.size(),
+ output_c_names_.data(),
+ output_val_.data(),
+ output_val_.size());
+ }
+ catch (const Ort::Exception &e)
+ {
+ qDebug() << "Failed to run the model: " << e.what();
+ return {};
+ }
+
+ last_inference_time_ = t.elapsed_ms();
+
+ // Perform coordinate transformation.
+ // From patch-local normalized in [-1,1] to
+ // frame unnormalized pixel.
+
+ cv::Matx33f center_size_cov_tril = {};
+ if (has_uncertainty_)
+ {
+ if (pos_scale_uncertainty_is_matrix_)
+ {
+ center_size_cov_tril = output_coord_scales_tril_;
+ }
+ else
+ {
+ center_size_cov_tril(0,0) = output_coord_scales_std_[0];
+ center_size_cov_tril(1,1) = output_coord_scales_std_[1];
+ center_size_cov_tril(2,2) = output_coord_scales_std_[2];
+ }
+ center_size_cov_tril *= patch_size*0.5f;
+ }
+
+ const cv::Point2f center = patch_center +
+ (0.5f*patch_size)*cv::Point2f{output_coord_[0], output_coord_[1]};
+ const float size = patch_size*0.5f*output_coord_[2];
+
+ // Following Eigen which uses quat components in the order w, x, y, z.
+ // As does OpenCV
+ cv::Quatf rotation = {
+ output_quat_[3],
+ output_quat_[0],
+ output_quat_[1],
+ output_quat_[2] };
+
+ // Should be lower triangular. If not maybe something is wrong with memory layout ... or the model.
+ assert(output_rotaxis_scales_tril_(0, 1) == 0);
+ assert(output_rotaxis_scales_tril_(0, 2) == 0);
+ assert(output_rotaxis_scales_tril_(1, 2) == 0);
+ assert(center_size_cov_tril(0, 1) == 0);
+ assert(center_size_cov_tril(0, 2) == 0);
+ assert(center_size_cov_tril(1, 2) == 0);
+
+ cv::Matx33f rotaxis_scales_tril = output_rotaxis_scales_tril_;
+
+ if (model_version_ < 2)
+ {
+ // Due to a change in coordinate conventions
+ rotation = world_to_image(rotation);
+ }
+
+ const cv::Rect2f outbox = {
+ patch_center.x + (0.5f*patch_size)*output_box_[0],
+ patch_center.y + (0.5f*patch_size)*output_box_[1],
+ 0.5f*patch_size*(output_box_[2]-output_box_[0]),
+ 0.5f*patch_size*(output_box_[3]-output_box_[1])
+ };
+
+ return std::optional<Face>({
+ rotation, rotaxis_scales_tril, outbox, center, size, center_size_cov_tril
+ });
+}
+
+
+cv::Mat PoseEstimator::last_network_input() const
+{
+ assert(!input_mat_.empty());
+ cv::Mat ret;
+ input_mat_.convertTo(ret, CV_8U, 255., 127.);
+ cv::cvtColor(ret, ret, cv::COLOR_GRAY2RGB);
+ return ret;
+}
+
+
+double PoseEstimator::last_inference_time_millis() const
+{
+ return last_inference_time_;
+}
+
+
+
+
+
+} // namespace neuralnet_tracker_ns
diff --git a/tracker-neuralnet/model_adapters.h b/tracker-neuralnet/model_adapters.h
new file mode 100644
index 00000000..c1aaa6de
--- /dev/null
+++ b/tracker-neuralnet/model_adapters.h
@@ -0,0 +1,105 @@
+#pragma once
+
+#include <optional>
+#include <array>
+#include <vector>
+#include <string>
+
+#include <onnxruntime_cxx_api.h>
+#include <opencv2/core.hpp>
+#include "opencv_contrib.h"
+
+
+namespace neuralnet_tracker_ns
+{
+
+// Generally useful sigmoid function
+float sigmoid(float x);
+
+
+class Localizer
+{
+ public:
+ Localizer(Ort::MemoryInfo &allocator_info,
+ Ort::Session &&session);
+
+ // Returns bounding wrt image coordinate of the input image
+ // The preceeding float is the score for being a face normalized to [0,1].
+ std::pair<float, cv::Rect2f> run(
+ const cv::Mat &frame);
+
+ double last_inference_time_millis() const;
+ private:
+ inline static constexpr int INPUT_IMG_WIDTH = 288;
+ inline static constexpr int INPUT_IMG_HEIGHT = 224;
+ Ort::Session session_{nullptr};
+ // Inputs / outputs
+ cv::Mat scaled_frame_{}, input_mat_{};
+ Ort::Value input_val_{nullptr}, output_val_{nullptr};
+ std::array<float, 5> results_;
+ double last_inference_time_ = 0;
+};
+
+
+class PoseEstimator
+{
+ public:
+ struct Face
+ {
+ cv::Quatf rotation;
+ cv::Matx33f rotaxis_cov_tril; // Lower triangular factor of Cholesky decomposition
+ cv::Rect2f box;
+ cv::Point2f center;
+ float size;
+ cv::Matx33f center_size_cov_tril; // Lower triangular factor of Cholesky decomposition
+ };
+
+ PoseEstimator(Ort::MemoryInfo &allocator_info,
+ Ort::Session &&session);
+ /** Inference
+ *
+ * Coordinates are defined wrt. the image space of the input `frame`.
+ * X goes right, Z (depth) into the image, Y points down (like pixel coordinates values increase from top to bottom)
+ */
+ std::optional<Face> run(const cv::Mat &frame, const cv::Rect &box);
+ // Returns an image compatible with the 'frame' image for displaying.
+ cv::Mat last_network_input() const;
+ double last_inference_time_millis() const;
+ bool has_uncertainty() const { return has_uncertainty_; }
+
+ private:
+ std::string get_network_input_name(size_t i) const;
+ std::string get_network_output_name(size_t i) const;
+ int64_t model_version_ = 0; // Queried meta data from the ONNX file
+ Ort::Session session_{nullptr}; // ONNX's runtime context for running the model
+ mutable Ort::Allocator allocator_; // Memory allocator for tensors
+ // Inputs
+ cv::Mat scaled_frame_{}, input_mat_{}; // Input. One is the original crop, the other is rescaled (?)
+ std::vector<Ort::Value> input_val_; // Tensors to put into the model
+ std::vector<std::string> input_names_; // Refers to the names in the onnx model.
+ std::vector<const char *> input_c_names_; // Refers to the C names in the onnx model.
+ // Outputs
+ cv::Vec<float, 3> output_coord_{}; // 2d Coordinate and head size output.
+ cv::Vec<float, 4> output_quat_{}; // Quaternion output
+ cv::Vec<float, 4> output_box_{}; // Bounding box output
+ cv::Matx33f output_rotaxis_scales_tril_{}; // Lower triangular matrix of LLT factorization of covariance of rotation vector as offset from output quaternion
+ cv::Matx33f output_coord_scales_tril_{}; // Lower triangular factor
+ cv::Vec3f output_coord_scales_std_{}; // Depending on the model, alternatively a 3d vector with standard deviations.
+ std::vector<Ort::Value> output_val_; // Tensors to put the model outputs in.
+ std::vector<std::string> output_names_; // Refers to the names in the onnx model.
+ std::vector<const char *> output_c_names_; // Refers to the C names in the onnx model.
+ // More bookkeeping
+ double last_inference_time_ = 0;
+ bool has_uncertainty_ = false;
+ bool pos_scale_uncertainty_is_matrix_ = false;
+};
+
+
+// Finds the intensity where x percent of pixels have less intensity than that.
+int find_input_intensity_quantile(const cv::Mat& frame, float percentage);
+
+// Adjust brightness levels to full range and scales the value range to [-0.5, 0.5]
+void normalize_brightness(const cv::Mat& frame, cv::Mat& out);
+
+
+} // namespace neuralnet_tracker_ns
diff --git a/tracker-neuralnet/models/head-localizer.onnx b/tracker-neuralnet/models/head-localizer.onnx
new file mode 100644
index 00000000..c128f89d
--- /dev/null
+++ b/tracker-neuralnet/models/head-localizer.onnx
Binary files differ
diff --git a/tracker-neuralnet/models/head-pose-0.2-big.onnx b/tracker-neuralnet/models/head-pose-0.2-big.onnx
new file mode 100644
index 00000000..e53fd831
--- /dev/null
+++ b/tracker-neuralnet/models/head-pose-0.2-big.onnx
Binary files differ
diff --git a/tracker-neuralnet/models/head-pose-0.2-small.onnx b/tracker-neuralnet/models/head-pose-0.2-small.onnx
new file mode 100644
index 00000000..f2b64219
--- /dev/null
+++ b/tracker-neuralnet/models/head-pose-0.2-small.onnx
Binary files differ
diff --git a/tracker-neuralnet/models/head-pose-0.3-big-quantized.onnx b/tracker-neuralnet/models/head-pose-0.3-big-quantized.onnx
new file mode 100644
index 00000000..7f875c63
--- /dev/null
+++ b/tracker-neuralnet/models/head-pose-0.3-big-quantized.onnx
Binary files differ
diff --git a/tracker-neuralnet/neuralnet-tracker.qrc b/tracker-neuralnet/neuralnet-tracker.qrc
new file mode 100644
index 00000000..d30ec313
--- /dev/null
+++ b/tracker-neuralnet/neuralnet-tracker.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>images/neuralnet.png</file>
+ </qresource>
+</RCC>
diff --git a/tracker-neuralnet/neuralnet-trackercontrols.ui b/tracker-neuralnet/neuralnet-trackercontrols.ui
new file mode 100644
index 00000000..ae2450b4
--- /dev/null
+++ b/tracker-neuralnet/neuralnet-trackercontrols.ui
@@ -0,0 +1,697 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Form</class>
+ <widget class="QWidget" name="Form">
+ <property name="windowModality">
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>651</width>
+ <height>432</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Tracker settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="4" column="0">
+ <widget class="QGroupBox" name="groupBox_10">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFillBackground">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Head Center Offset</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="0" column="0">
+ <widget class="QFrame" name="frame_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_11">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <property name="verticalSpacing">
+ <number>0</number>
+ </property>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_66">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Right</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_61">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Forward</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_62">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Up</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="tx_spin">
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="ty_spin">
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSpinBox" name="tz_spin">
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QFrame" name="frame_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>260</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_59">
+ <property name="text">
+ <string>Use only yaw and pitch while calibrating.
+Don't roll or change position.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="sample_count_display">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::Panel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="tcalib_button">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Start calibration</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="8" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QGroupBox" name="groupBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFillBackground">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Camera Configuration</string>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>10</number>
+ </property>
+ <property name="bottomMargin">
+ <number>8</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>0</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>2</number>
+ </property>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cameraName">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>Diagonal FOV</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>Camera name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="cameraFOV">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Field of view. Needed to transform the pose to world coordinates.</string>
+ </property>
+ <property name="locale">
+ <locale language="English" country="UnitedStates"/>
+ </property>
+ <property name="minimum">
+ <number>35</number>
+ </property>
+ <property name="maximum">
+ <number>90</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="resolution">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>The requested resolution for cases where the camera delivers maximum frame rate only for a particular resolution. The image may still be downscaled to the internal resolution.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="resolution_label">
+ <property name="text">
+ <string>Resolution</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_6">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>0</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>2</number>
+ </property>
+ <item row="4" column="1">
+ <widget class="QComboBox" name="cameraFPS">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Requested video frame rate. Actual setting may not be supported by the camera.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_12">
+ <property name="text">
+ <string>Frames per second</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QCheckBox" name="use_mjpeg">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_11">
+ <property name="text">
+ <string>MJPEG</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="camera_settings">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Camera settings</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QLabel" name="resolution_display">
+ <property name="autoFillBackground">
+ <bool>true</bool>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::Panel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QGroupBox" name="tuningOptionsBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="autoFillBackground">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Tuning / Debug</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="threadCountLabel">
+ <property name="text">
+ <string>Thread Count</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="threadCount">
+ <property name="toolTip">
+ <string>Number of threads. Can be used to balance the CPU load between the game and the tracker.</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>32</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="showNetworkInput">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Show the image patch that the pose estimation model sees.</string>
+ </property>
+ <property name="text">
+ <string>Show Network Input</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="roiFilterAlphaLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>ROI Smoothing Alpha</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="roiFilterAlpha">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Amount of smoothing of the face region coordinates. Can help stabilize the pose.</string>
+ </property>
+ <property name="wrapping">
+ <bool>false</bool>
+ </property>
+ <property name="decimals">
+ <number>2</number>
+ </property>
+ <property name="maximum">
+ <double>1.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.010000000000000</double>
+ </property>
+ <property name="value">
+ <double>1.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="roiZoomLabel">
+ <property name="text">
+ <string>ROI Zoom</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="roiZoom">
+ <property name="toolTip">
+ <string>Zoom factor for the face region. Applied before the patch is fed into the pose estimation model. There is a sweet spot near 1.</string>
+ </property>
+ <property name="minimum">
+ <double>0.100000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>2.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.010000000000000</double>
+ </property>
+ <property name="value">
+ <double>1.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QFrame" name="network_select_frame">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="posenetSelectButton">
+ <property name="toolTip">
+ <string>Select the pose network. Changes take affect on the next tracker start</string>
+ </property>
+ <property name="text">
+ <string>Select Pose Net ONNX</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="posenetFileDisplay">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&lt;the pose net file&gt;</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+ <designerdata>
+ <property name="gridDeltaX">
+ <number>10</number>
+ </property>
+ <property name="gridDeltaY">
+ <number>10</number>
+ </property>
+ <property name="gridSnapX">
+ <bool>false</bool>
+ </property>
+ <property name="gridSnapY">
+ <bool>false</bool>
+ </property>
+ <property name="gridVisible">
+ <bool>true</bool>
+ </property>
+ </designerdata>
+</ui>
diff --git a/tracker-neuralnet/opencv_contrib.h b/tracker-neuralnet/opencv_contrib.h
new file mode 100644
index 00000000..1c199025
--- /dev/null
+++ b/tracker-neuralnet/opencv_contrib.h
@@ -0,0 +1,120 @@
+#pragma once
+
+#include <opencv2/core.hpp>
+#include <opencv2/core/base.hpp>
+#include <opencv2/core/quaternion.hpp>
+
+// Well eventually it might be a contribution
+
+namespace cvcontrib
+{
+
+
+template<class T>
+cv::Point_<T> as_point(const cv::Size_<T>& s)
+{
+ return { s.width, s.height };
+}
+
+
+template<class T>
+cv::Size_<T> as_size(const cv::Point_<T>& p)
+{
+ return { p.x, p.y };
+}
+
+
+template<int n, int m>
+inline bool allfinite(const cv::Matx<float, n, m> &mat)
+{
+ const size_t sz = mat.rows*mat.cols;
+ for (size_t i=0; i<sz; ++i)
+ if (!std::isfinite(mat.val[i]))
+ return false;
+ return true;
+}
+
+
+// Because compiler refuses to convert it automatically
+template<int n>
+inline cv::Vec<float, n> to_vec(const cv::Matx<float, n, 1>& m)
+{
+ return cv::Vec<float,n>{m.val};
+}
+
+
+template<int n, int m, int o>
+inline void set_minor(cv::Vec<float, m> &dst, const int startrow, const cv::Matx<float, o, 1> &src)
+{
+ assert (startrow>=0 && startrow+n <= dst.rows);
+ for (int row=startrow, i=0; row<startrow+n; ++row,++i)
+ {
+ dst[row] = src(i,0);
+ }
+}
+
+
+template<int nrows, int ncols, int m, int n>
+inline void set_minor(cv::Matx<float, m, n>& dst, const int startrow, int startcol, const cv::Matx<float, nrows, ncols> &src)
+{
+ assert (startrow>=0 && startrow+nrows <= dst.rows);
+ assert (startcol>=0 && startcol+ncols <= dst.cols);
+ for (int row=startrow, i=0; row<startrow+nrows; ++row,++i)
+ {
+ for (int col=startcol, j=0; col<startcol+ncols; ++col,++j)
+ {
+ dst(row, col) = src(i,j);
+ }
+ }
+}
+
+
+inline cv::Quatf identity_quat()
+{
+ return cv::Quatf(1,0,0,0);
+}
+
+
+inline cv::Vec3f toRotVec(const cv::Quatf& q)
+{
+ // This is an improved implementation
+#if 1
+ // w = cos(alpha/2)
+ // xyz = sin(alpha/2)*axis
+ static constexpr float eps = 1.e-12f;
+ const cv::Vec3f xyz{q.x, q.y, q.z};
+ const float len = cv::norm(xyz);
+ const float angle = std::atan2(len, q.w)*2.f;
+ return xyz*(angle/(len+eps));
+#else
+ // The opencv implementation fails even the simplest test:
+ // out = toRVec(cv::Quatf{1., 0., 0., 0. });
+ // ASSERT_TRUE(std::isfinite(out[0]) && std::isfinite(out[1]) && std::isfinite(out[2]));
+ return q.toRotVec();
+#endif
+}
+
+
+inline cv::Vec3f rotate(const cv::Quatf& q, const cv::Vec3f &v)
+{
+ const auto r = q * cv::Quatf{0., v[0], v[1], v[2]} * q.conjugate();
+ return { r.x, r.y, r.z };
+}
+
+
+template<int n>
+inline cv::Matx<float, n, n> cholesky(const cv::Matx<float, n, n>& mat)
+{
+ cv::Matx<float, n, n> l = mat;
+ // Der Code ist die Doku!
+ // https://github.com/opencv/opencv/blob/4.5.4/modules/core/src/matrix_decomp.cpp#L95
+ cv::Cholesky(l.val, l.cols * sizeof(float), n, nullptr, 0, 0);
+ // It doesn't clear the upper triangle so we do it for it.
+ for (int row=0; row<n; ++row)
+ for (int col=row+1; col<n; ++col)
+ l(row, col) = 0.f;
+ return l;
+}
+
+
+} // namespace cvcontrib \ No newline at end of file
diff --git a/tracker-neuralnet/preview.cpp b/tracker-neuralnet/preview.cpp
new file mode 100644
index 00000000..76a6bbc0
--- /dev/null
+++ b/tracker-neuralnet/preview.cpp
@@ -0,0 +1,135 @@
+#include "preview.h"
+
+
+namespace neuralnet_tracker_ns
+{
+
+
+cv::Rect make_crop_rect_for_aspect(const cv::Size &size, int aspect_w, int aspect_h)
+{
+ auto [w, h] = size;
+ if ( w*aspect_h > aspect_w*h )
+ {
+ // Image is too wide
+ const int new_w = (aspect_w*h)/aspect_h;
+ return cv::Rect((w - new_w)/2, 0, new_w, h);
+ }
+ else
+ {
+ const int new_h = (aspect_h*w)/aspect_w;
+ return cv::Rect(0, (h - new_h)/2, w, new_h);
+ }
+}
+
+
+
+
+void Preview::init(const cv_video_widget& widget)
+{
+ auto [w,h] = widget.preview_size();
+ preview_size_ = { w, h };
+}
+
+
+void Preview::copy_video_frame(const cv::Mat& frame)
+{
+ cv::Rect roi = make_crop_rect_for_aspect(frame.size(), preview_size_.width, preview_size_.height);
+
+ cv::resize(frame(roi), preview_image_, preview_size_, 0, 0, cv::INTER_NEAREST);
+
+ offset_ = { (float)-roi.x, (float)-roi.y };
+ scale_ = float(preview_image_.cols) / float(roi.width);
+}
+
+
+void Preview::draw_gizmos(
+ const std::optional<PoseEstimator::Face> &face,
+ const std::optional<cv::Rect2f>& last_roi,
+ const std::optional<cv::Rect2f>& last_localizer_roi,
+ const cv::Point2f& neckjoint_position)
+{
+ if (preview_image_.empty())
+ return;
+
+ if (last_roi)
+ {
+ const int col = 255;
+ cv::rectangle(preview_image_, transform(*last_roi), cv::Scalar(0, col, 0), /*thickness=*/1);
+ }
+ if (last_localizer_roi)
+ {
+ const int col = 255;
+ cv::rectangle(preview_image_, transform(*last_localizer_roi), cv::Scalar(col, 0, 255-col), /*thickness=*/1);
+ }
+
+ if (face)
+ {
+ if (face->size>=1.f)
+ cv::circle(preview_image_, static_cast<cv::Point>(transform(face->center)), int(transform(face->size)), cv::Scalar(255,255,255), 2);
+ cv::circle(preview_image_, static_cast<cv::Point>(transform(face->center)), 3, cv::Scalar(255,255,255), -1);
+
+ const cv::Matx33f R = face->rotation.toRotMat3x3(cv::QUAT_ASSUME_UNIT);
+
+ auto draw_coord_line = [&](int i, const cv::Scalar& color)
+ {
+ const float vx = R(0,i);
+ const float vy = R(1,i);
+ static constexpr float len = 100.f;
+ cv::Point q = face->center + len*cv::Point2f{vx, vy};
+ cv::line(preview_image_, static_cast<cv::Point>(transform(face->center)), static_cast<cv::Point>(transform(q)), color, 2);
+ };
+ draw_coord_line(0, {0, 0, 255});
+ draw_coord_line(1, {0, 255, 0});
+ draw_coord_line(2, {255, 0, 0});
+
+ // Draw the computed joint position
+ auto xy = transform(neckjoint_position);
+ cv::circle(preview_image_, cv::Point(xy.x,xy.y), 5, cv::Scalar(0,0,255), -1);
+ }
+
+
+}
+
+void Preview::overlay_netinput(const cv::Mat& netinput)
+{
+ if (netinput.empty())
+ return;
+
+ const int w = std::min(netinput.cols, preview_image_.cols);
+ const int h = std::min(netinput.rows, preview_image_.rows);
+ cv::Rect roi(0, 0, w, h);
+ netinput(roi).copyTo(preview_image_(roi));
+}
+
+void Preview::draw_fps(double fps, double last_inference_time)
+{
+ char buf[128];
+ ::snprintf(buf, sizeof(buf), "%d Hz, pose inference: %d ms", std::clamp(int(fps), 0, 9999), int(last_inference_time));
+ cv::putText(preview_image_, buf, cv::Point(10, preview_image_.rows-10), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0, 255, 0), 1);
+}
+
+
+void Preview::copy_to_widget(cv_video_widget& widget)
+{
+ if (preview_image_.rows > 0)
+ widget.update_image(preview_image_);
+}
+
+
+cv::Rect2f Preview::transform(const cv::Rect2f& r) const
+{
+ return { (r.x - offset_.x)*scale_, (r.y - offset_.y)*scale_, r.width*scale_, r.height*scale_ };
+}
+
+cv::Point2f Preview::transform(const cv::Point2f& p) const
+{
+ return { (p.x - offset_.x)*scale_ , (p.y - offset_.y)*scale_ };
+}
+
+float Preview::transform(float s) const
+{
+ return s * scale_;
+}
+
+
+} \ No newline at end of file
diff --git a/tracker-neuralnet/preview.h b/tracker-neuralnet/preview.h
new file mode 100644
index 00000000..adc12993
--- /dev/null
+++ b/tracker-neuralnet/preview.h
@@ -0,0 +1,60 @@
+/* Copyright (c) 2021 Michael Welter <michael@welter-4d.de>
+ *
+ * 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 "model_adapters.h"
+
+#include "cv/video-widget.hpp"
+
+#include <optional>
+
+#include <opencv2/core.hpp>
+#include <opencv2/imgproc.hpp>
+
+
+namespace neuralnet_tracker_ns
+{
+
+/** Makes a maximum size cropping rect with the given aspect.
+* @param aspect_w: nominator of the aspect ratio
+* @param aspect_h: denom of the aspect ratio
+*/
+cv::Rect make_crop_rect_for_aspect(const cv::Size &size, int aspect_w, int aspect_h);
+
+
+/** This class is responsible for drawing the debug/info gizmos
+*
+* In addition there function to transform the inputs to the size of
+* the preview image which can be different from the camera frame.
+*/
+class Preview
+{
+public:
+ void init(const cv_video_widget& widget);
+ void copy_video_frame(const cv::Mat& frame);
+ void draw_gizmos(
+ const std::optional<PoseEstimator::Face> &face,
+ const std::optional<cv::Rect2f>& last_roi,
+ const std::optional<cv::Rect2f>& last_localizer_roi,
+ const cv::Point2f& neckjoint_position);
+ void overlay_netinput(const cv::Mat& netinput);
+ void draw_fps(double fps, double last_inference_time);
+ void copy_to_widget(cv_video_widget& widget);
+private:
+ // Transform from camera image to preview
+ cv::Rect2f transform(const cv::Rect2f& r) const;
+ cv::Point2f transform(const cv::Point2f& p) const;
+ float transform(float s) const;
+
+ cv::Mat preview_image_;
+ cv::Size preview_size_ = { 0, 0 };
+ float scale_ = 1.f;
+ cv::Point2f offset_ = { 0.f, 0.f};
+};
+
+} // neuralnet_tracker_ns \ No newline at end of file
diff --git a/tracker-neuralnet/redist/vcomp140.dll b/tracker-neuralnet/redist/vcomp140.dll
new file mode 100644
index 00000000..42c069b9
--- /dev/null
+++ b/tracker-neuralnet/redist/vcomp140.dll
Binary files differ
diff --git a/tracker-neuralnet/tests.cpp b/tracker-neuralnet/tests.cpp
new file mode 100644
index 00000000..b1d2a6d0
--- /dev/null
+++ b/tracker-neuralnet/tests.cpp
@@ -0,0 +1,58 @@
+#include "model_adapters.h"
+
+#include <algorithm>
+#include <numeric>
+#include <cstdio>
+
+namespace neuralnet_tracker_tests
+{
+
+
+void assert_(bool ok, const std::string& msg)
+{
+ if (ok)
+ return;
+ std::cout << msg << std::endl;
+ std::exit(-1);
+}
+
+
+void test_find_input_intensity_quantile()
+{
+ cv::Mat data(10,10, CV_8UC1);
+ std::iota(data.begin<uint8_t>(), data.end<uint8_t>(), 0);
+
+ const float pct = 90;
+
+ const int val = neuralnet_tracker_ns::find_input_intensity_quantile(data, pct);
+
+ assert_(val == int(10*10*pct/100.f), "test_find_input_intensity_quantile failed");
+}
+
+
+void test_normalize_brightness()
+{
+ cv::Mat data(10,10, CV_8UC1);
+ std::iota(data.begin<uint8_t>(), data.end<uint8_t>(), 0);
+
+ cv::Mat out;
+ neuralnet_tracker_ns::normalize_brightness(data, out);
+
+ auto [minit,maxit] = std::minmax_element(out.begin<float>(),out.end<float>());
+ const auto minval = *minit;
+ const auto maxval = *maxit;
+ assert_(std::abs(minval + 0.5f) < 0.02, "test_normalize_brightness failed");
+ // If the brightest value is lower than half-max, it will be boosted to half-max.
+ // Otherwise it will just be rescaled to [-.5, 0.5 ]. Here we have the low-brightness case.
+ assert_(std::abs(maxval - 0.0f) < 0.02, "test_normalize_brightness failed");
+}
+
+
+void run()
+{
+ test_find_input_intensity_quantile();
+ test_normalize_brightness();
+}
+
+
+} \ No newline at end of file
diff --git a/tracker-neuralnet/unscented_trafo.h b/tracker-neuralnet/unscented_trafo.h
new file mode 100644
index 00000000..267aa969
--- /dev/null
+++ b/tracker-neuralnet/unscented_trafo.h
@@ -0,0 +1,132 @@
+#pragma once
+
+#include <algorithm>
+#include <opencv2/core.hpp>
+#include <opencv2/core/base.hpp>
+#include <opencv2/core/quaternion.hpp>
+
+#include <cmath>
+#include <vector>
+
+#include "opencv_contrib.h"
+
+namespace ukf_cv
+{
+
+using namespace cvcontrib;
+
+template<int dim, int otherdim = dim>
+using SigmaPoints = std::array<cv::Vec<float,otherdim>,dim*2+1>;
+
+
+// Ported from
+// https://filterpy.readthedocs.io/en/latest/_modules/filterpy/kalman/sigma_points.html
+// Excerpt from the original docu:
+// "
+
+// Generates sigma points and weights according to Van der Merwe's
+// 2004 dissertation[1] for the UnscentedKalmanFilter class.. It
+// parametizes the sigma points using alpha, beta, kappa terms, and
+// is the version seen in most publications.
+
+// Unless you know better, this should be your default choice.
+
+// alpha : float
+// Determins the spread of the sigma points around the mean.
+// Usually a small positive value (1e-3) according to [3].
+
+// beta : float
+// Incorporates prior knowledge of the distribution of the mean. For
+// Gaussian x beta=2 is optimal, according to [3].
+
+// kappa : float, default=0.0
+// Secondary scaling parameter usually set to 0 according to [4],
+// or to 3-n according to [5].
+
+// Reference
+// .. [1] R. Van der Merwe "Sigma-Point Kalman Filters for Probabilitic
+// Inference in Dynamic State-Space Models" (Doctoral dissertation)
+
+// "
+template<int dim>
+class MerweScaledSigmaPoints
+{
+public:
+ static constexpr int num_sigmas = 2*dim+1;
+
+ using Vector = cv::Vec<float,dim>;
+ using Matrix = cv::Matx<float,dim,dim>;
+
+ MerweScaledSigmaPoints(float alpha = 0.01, float beta = 2., int kappa = 3-dim)
+ {
+ lambda = alpha*alpha * (dim + kappa) - dim;
+ const float c = .5 / (dim + lambda);
+ Wc_i = c;
+ Wm_i = c;
+ Wm_0 = lambda / (dim+lambda);
+ Wc_0 = Wm_0 + (1.-alpha*alpha + beta);
+ }
+
+ SigmaPoints<dim> compute_sigmas(const Vector &mu, const Matrix &mat, bool is_tril_factor) const
+ {
+ const Matrix triu_factor = is_tril_factor ? mat.t() : cholesky(mat).t();
+
+ const Matrix U = triu_factor*std::sqrt(lambda+dim);
+
+ SigmaPoints<dim> sigmas;
+
+ sigmas[0] = mu;
+ for (int k=0; k<dim; ++k)
+ {
+ sigmas[k+1] = to_vec(mu + U.row(k).t());
+ sigmas[dim+k+1] = to_vec(mu - U.row(k).t());
+ }
+ return sigmas;
+ }
+
+ template<int otherdim>
+ std::tuple<cv::Vec<float,otherdim> , cv::Matx<float,otherdim,otherdim>> compute_statistics(const SigmaPoints<dim,otherdim> &sigmas) const
+ {
+ cv::Vec<float,otherdim> mu{}; // Zero initializes
+ for (size_t i=0; i<sigmas.size(); ++i)
+ {
+ mu += to_vec((i==0 ? Wm_0 : Wm_i) * sigmas[i]);
+ }
+
+ cv::Matx<float,otherdim,otherdim> cov{};
+ for (size_t i=0; i<sigmas.size(); ++i)
+ {
+ const auto p = sigmas[i] - mu;
+ cov += (i==0 ? Wc_0 : Wc_i)*p*p.t();
+ }
+
+ return { mu, cov };
+ }
+
+ template<int otherdim>
+ cv::Matx<float,dim,otherdim> compute_cov(const SigmaPoints<dim,dim> &sigmas, const SigmaPoints<dim,otherdim> &othersigmas) const
+ {
+ cv::Vec<float,dim> mu{}; // Zero initializes
+ cv::Vec<float,otherdim> mu_other{}; // Zero initializes
+ for (size_t i=0; i<sigmas.size(); ++i)
+ {
+ mu += to_vec((i==0 ? Wm_0 : Wm_i) * sigmas[i]);
+ mu_other += to_vec((i==0 ? Wm_0 : Wm_i) * othersigmas[i]);
+ }
+
+ cv::Matx<float,dim,otherdim> cov{};
+ for (size_t i=0; i<sigmas.size(); ++i)
+ {
+ const auto p = sigmas[i] - mu;
+ const auto q = othersigmas[i] - mu_other;
+ cov += (i==0 ? Wc_0 : Wc_i)*p*q.t();
+ }
+
+ return cov;
+ }
+private:
+ float Wc_i, Wm_i, Wm_0, Wc_0, lambda;
+};
+
+
+} // namespace ukf_cv \ No newline at end of file
diff --git a/tracker-pt/CMakeLists.txt b/tracker-pt/CMakeLists.txt
index ae923d3d..0c2e9ce3 100644
--- a/tracker-pt/CMakeLists.txt
+++ b/tracker-pt/CMakeLists.txt
@@ -1,9 +1,12 @@
-find_package(OpenCV 3.0 QUIET)
-set(modules opencv_core opencv_videoio opencv_imgproc)
+include(opentrack-opencv)
+find_package(OpenCV QUIET)
if(OpenCV_FOUND)
+ foreach(k core)
+ otr_install_lib("opencv_${k}" "${opentrack-libexec}")
+ endforeach()
otr_module(tracker-pt-base STATIC)
- target_include_directories(opentrack-tracker-pt-base SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS})
- target_link_libraries(opentrack-tracker-pt-base opentrack-cv ${modules})
+ target_include_directories(${self} SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS})
+ target_link_libraries(${self} opentrack-cv opencv_core)
+ set_property(TARGET ${self} PROPERTY OUTPUT_NAME "pt-base")
endif()
add_subdirectory(module)
-
diff --git a/tracker-pt/FTNoIR_PT_Controls.ui b/tracker-pt/FTNoIR_PT_Controls.ui
index 366f219c..53a29c1a 100644
--- a/tracker-pt/FTNoIR_PT_Controls.ui
+++ b/tracker-pt/FTNoIR_PT_Controls.ui
@@ -9,12 +9,12 @@
<rect>
<x>0</x>
<y>0</y>
- <width>418</width>
- <height>724</height>
+ <width>413</width>
+ <height>630</height>
</rect>
</property>
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@@ -32,85 +32,24 @@
<property name="autoFillBackground">
<bool>false</bool>
</property>
- <layout class="QGridLayout" name="gridLayout_9">
- <property name="sizeConstraint">
- <enum>QLayout::SetFixedSize</enum>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>0</number>
</property>
- <item row="1" column="0" alignment="Qt::AlignVCenter">
- <widget class="QGroupBox" name="groupBox_5">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Status</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_10">
- <item row="1" column="0">
- <widget class="QLabel" name="label_3">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Extracted Points:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_38">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Camera Info:</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="pointinfo_label">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="caminfo_label">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="0" column="0">
+ <property name="leftMargin">
+ <number>4</number>
+ </property>
+ <property name="topMargin">
+ <number>4</number>
+ </property>
+ <property name="rightMargin">
+ <number>4</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
<widget class="QTabWidget" name="tabWidget">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
@@ -118,6 +57,12 @@
<number>0</number>
</property>
<widget class="QWidget" name="tab_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<attribute name="title">
<string>Camera</string>
</attribute>
@@ -134,64 +79,100 @@
<string>Camera settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="1">
- <widget class="QComboBox" name="camdevice_combo">
+ <item row="11" column="1">
+ <widget class="QCheckBox" name="chroma_key_overexposed">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_4">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="minimumContentsLength">
- <number>10</number>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="toolTip">
+ <string>This should be 56° or 76° for the PS3 Eye, dependent upon the physical lens setting. It's only neccessary to get position correspond to real-world values.</string>
+ </property>
+ <property name="text">
+ <string>Diagonal field of view</string>
</property>
</widget>
</item>
- <item row="4" column="1">
- <widget class="QSpinBox" name="fov">
+ <item row="6" column="0">
+ <widget class="QLabel" name="label_5">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="suffix">
- <string>°</string>
+ <property name="text">
+ <string>Dynamic pose (for caps only, never clips)</string>
</property>
- <property name="prefix">
- <string/>
+ </widget>
+ </item>
+ <item row="9" column="0">
+ <widget class="QLabel" name="label_12">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <property name="minimum">
- <number>10</number>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
</property>
- <property name="maximum">
- <number>90</number>
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Color channels used</string>
</property>
</widget>
</item>
- <item row="4" column="0">
- <widget class="QLabel" name="label_4">
+ <item row="8" column="1">
+ <widget class="QPushButton" name="camera_settings">
<property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
- <string>Diagonal field of view</string>
+ <string>Open</string>
</property>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_36">
+ <item row="4" column="1">
+ <widget class="QCheckBox" name="use_mjpeg">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_13">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="toolTip">
+ <string>Enable MJPEG compression for high-speed cameras other than the PS3 Eye. Windows only.</string>
+ </property>
<property name="text">
- <string>Width</string>
+ <string>MJPEG compression</string>
</property>
</widget>
</item>
@@ -211,30 +192,28 @@
</property>
</widget>
</item>
- <item row="2" column="1">
- <widget class="QSpinBox" name="res_y_spin">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_36">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="toolTip">
- <string>Desired capture height</string>
- </property>
- <property name="suffix">
- <string> px</string>
- </property>
- <property name="maximum">
- <number>2000</number>
+ <property name="text">
+ <string>Width</string>
</property>
- <property name="singleStep">
- <number>10</number>
+ </widget>
+ </item>
+ <item row="11" column="0">
+ <widget class="QLabel" name="label_16">
+ <property name="text">
+ <string>Chroma key includes overexposed pixels</string>
</property>
</widget>
</item>
- <item row="6" column="0">
- <widget class="QLabel" name="label_6">
+ <item row="8" column="0">
+ <widget class="QLabel" name="label_9">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
@@ -242,30 +221,30 @@
</sizepolicy>
</property>
<property name="text">
- <string>Dynamic pose timeout</string>
+ <string>Camera settings (when available)</string>
</property>
</widget>
</item>
- <item row="3" column="1">
- <widget class="QSpinBox" name="fps_spin">
+ <item row="7" column="1">
+ <widget class="QSpinBox" name="init_phase_timeout">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="toolTip">
- <string>Desired capture framerate</string>
- </property>
<property name="suffix">
- <string> Hz</string>
+ <string> ms</string>
+ </property>
+ <property name="minimum">
+ <number>50</number>
</property>
<property name="maximum">
- <number>2000</number>
+ <number>5000</number>
</property>
</widget>
</item>
- <item row="5" column="1">
+ <item row="6" column="1">
<widget class="QCheckBox" name="dynamic_pose">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
@@ -300,8 +279,8 @@
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_41">
+ <item row="7" column="0">
+ <widget class="QLabel" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
@@ -309,26 +288,20 @@
</sizepolicy>
</property>
<property name="text">
- <string>Height</string>
+ <string>Dynamic pose timeout</string>
</property>
</widget>
</item>
- <item row="6" column="1">
- <widget class="QSpinBox" name="init_phase_timeout">
+ <item row="0" column="1">
+ <widget class="QComboBox" name="camdevice_combo">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="suffix">
- <string> ms</string>
- </property>
- <property name="minimum">
- <number>50</number>
- </property>
- <property name="maximum">
- <number>5000</number>
+ <property name="minimumContentsLength">
+ <number>10</number>
</property>
</widget>
</item>
@@ -348,46 +321,39 @@
</property>
</widget>
</item>
- <item row="7" column="1">
- <widget class="QPushButton" name="camera_settings">
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_41">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
- <string>Open</string>
+ <string>Height</string>
</property>
</widget>
</item>
- <item row="7" column="0">
- <widget class="QLabel" name="label_9">
+ <item row="3" column="1">
+ <widget class="QSpinBox" name="fps_spin">
<property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="text">
- <string>Camera settings (when available)</string>
+ <property name="toolTip">
+ <string>Desired capture framerate</string>
</property>
- </widget>
- </item>
- <item row="8" column="0">
- <widget class="QLabel" name="label_12">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <property name="suffix">
+ <string> Hz</string>
</property>
- <property name="text">
- <string>Color channels used</string>
+ <property name="maximum">
+ <number>2000</number>
</property>
</widget>
</item>
- <item row="8" column="1">
+ <item row="9" column="1">
<widget class="QComboBox" name="blob_color">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
@@ -397,12 +363,12 @@
</property>
<item>
<property name="text">
- <string>Average</string>
+ <string>Grayscale BT.709</string>
</property>
</item>
<item>
<property name="text">
- <string>Natural</string>
+ <string>Grayscale (from hardware)</string>
</property>
</item>
<item>
@@ -412,24 +378,133 @@
</item>
<item>
<property name="text">
+ <string>Green only</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
<string>Blue only</string>
</property>
</item>
+ <item>
+ <property name="text">
+ <string>Red chroma key</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Green chroma key</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Blue chroma key</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Cyan chroma key</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Yellow chroma key</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Magenta chroma key</string>
+ </property>
+ </item>
</widget>
</item>
- <item row="5" column="0">
- <widget class="QLabel" name="label_5">
+ <item row="5" column="1">
+ <widget class="QSpinBox" name="fov">
<property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="suffix">
+ <string>°</string>
+ </property>
+ <property name="prefix">
+ <string/>
+ </property>
+ <property name="minimum">
+ <number>10</number>
+ </property>
+ <property name="maximum">
+ <number>90</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSpinBox" name="res_y_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
+ <property name="toolTip">
+ <string>Desired capture height</string>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="maximum">
+ <number>2000</number>
+ </property>
+ <property name="singleStep">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="10" column="0">
+ <widget class="QLabel" name="label_17">
<property name="text">
- <string>Dynamic pose (for caps only, never clips)</string>
+ <string>Chroma key strength</string>
</property>
</widget>
</item>
+ <item row="10" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QSlider" name="chroma_key_strength_slider">
+ <property name="minimum">
+ <number>5</number>
+ </property>
+ <property name="maximum">
+ <number>40</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="chroma_key_strength_label">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="minimum">
+ <double>0.500000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>4.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
</item>
@@ -469,6 +544,12 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="toolTip">
+ <string>Set minimum size to avoid small stray lights from being treated as points.</string>
+ </property>
<property name="text">
<string>Min size</string>
</property>
@@ -545,6 +626,12 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
+ <property name="cursor">
+ <cursorShape>WhatsThisCursor</cursorShape>
+ </property>
+ <property name="toolTip">
+ <string>Track dependent on point size and not absolute brightness. This may allow more stable tracking.</string>
+ </property>
<property name="text">
<string>Automatic threshold</string>
</property>
@@ -572,6 +659,26 @@
</property>
</widget>
</item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="threshold_value_display">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_14">
+ <property name="text">
+ <string>Value</string>
+ </property>
+ </widget>
+ </item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="mindiam_spin">
<property name="sizePolicy">
@@ -594,29 +701,22 @@
</property>
</widget>
</item>
- <item row="2" column="1">
- <widget class="QLabel" name="threshold_value_display">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_14">
- <property name="text">
- <string>Value</string>
- </property>
- </widget>
- </item>
</layout>
</widget>
</item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
</layout>
</widget>
<widget class="QWidget" name="tab_4">
@@ -1318,6 +1418,231 @@ Don't roll or change position.</string>
</layout>
</widget>
</item>
+ <item row="2" column="0">
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>Filter</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_9">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Point filter</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_12">
+ <item row="1" column="1">
+ <widget class="QSlider" name="point_filter_limit_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>99</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="pageStep">
+ <number>10</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QDoubleSpinBox" name="point_filter_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>2</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="maximum">
+ <double>999.990000000000009</double>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_15">
+ <property name="text">
+ <string>Limit</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QDoubleSpinBox" name="point_filter_deadzone_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="maximum">
+ <double>1.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSlider" name="point_filter_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>10</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>400</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="pageStep">
+ <number>10</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSlider" name="point_filter_deadzone_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>0</number>
+ </property>
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="pageStep">
+ <number>3</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_30">
+ <property name="text">
+ <string>Deadzone</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QDoubleSpinBox" name="point_filter_limit_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="maximum">
+ <double>1.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="enable_point_filter">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Filter point centers prior to pose estimation.</string>
+ </property>
+ <property name="text">
+ <string>Enable</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
</layout>
</widget>
<widget class="QWidget" name="tab_3">
@@ -1355,7 +1680,74 @@ Don't roll or change position.</string>
</widget>
</widget>
</item>
- <item row="2" column="0">
+ <item>
+ <widget class="QGroupBox" name="groupBox_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Status</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_10">
+ <item row="1" column="1">
+ <widget class="QLabel" name="pointinfo_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="caminfo_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Extracted Points:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_38">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Camera Info:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
@@ -1376,11 +1768,14 @@ Don't roll or change position.</string>
<tabstop>res_x_spin</tabstop>
<tabstop>res_y_spin</tabstop>
<tabstop>fps_spin</tabstop>
+ <tabstop>use_mjpeg</tabstop>
<tabstop>fov</tabstop>
<tabstop>dynamic_pose</tabstop>
<tabstop>init_phase_timeout</tabstop>
<tabstop>camera_settings</tabstop>
<tabstop>blob_color</tabstop>
+ <tabstop>chroma_key_strength_slider</tabstop>
+ <tabstop>chroma_key_overexposed</tabstop>
<tabstop>auto_threshold</tabstop>
<tabstop>threshold_slider</tabstop>
<tabstop>mindiam_spin</tabstop>
@@ -1391,8 +1786,8 @@ Don't roll or change position.</string>
<tabstop>clip_bheight_spin</tabstop>
<tabstop>clip_blength_spin</tabstop>
<tabstop>cap_length_spin</tabstop>
- <tabstop>cap_height_spin</tabstop>
<tabstop>cap_width_spin</tabstop>
+ <tabstop>cap_height_spin</tabstop>
<tabstop>m1x_spin</tabstop>
<tabstop>m1y_spin</tabstop>
<tabstop>m1z_spin</tabstop>
@@ -1403,9 +1798,25 @@ Don't roll or change position.</string>
<tabstop>ty_spin</tabstop>
<tabstop>tz_spin</tabstop>
<tabstop>tcalib_button</tabstop>
+ <tabstop>enable_point_filter</tabstop>
+ <tabstop>point_filter_slider</tabstop>
+ <tabstop>point_filter_limit_slider</tabstop>
+ <tabstop>point_filter_deadzone_slider</tabstop>
+ <tabstop>maxdiam_spin</tabstop>
+ <tabstop>mindiam_spin</tabstop>
+ <tabstop>auto_threshold</tabstop>
+ <tabstop>threshold_slider</tabstop>
</tabstops>
<resources>
<include location="module/tracker_pt.qrc"/>
+ <include location="module/tracker_pt.qrc"/>
+ <include location="module/tracker_pt.qrc"/>
+ <include location="module/tracker_pt.qrc"/>
+ <include location="module/tracker_pt.qrc"/>
+ <include location="tracker_pt_base.qrc"/>
+ <include location="tracker_pt_base.qrc"/>
+ <include location="tracker_pt_base.qrc"/>
+ <include location="tracker_pt_base.qrc"/>
<include location="tracker_pt_base.qrc"/>
</resources>
<connections/>
diff --git a/tracker-pt/export.hpp b/tracker-pt/export.hpp
deleted file mode 100644
index cef63f83..00000000
--- a/tracker-pt/export.hpp
+++ /dev/null
@@ -1,15 +0,0 @@
-// generates export.hpp for each module from compat/linkage.hpp
-
-#pragma once
-
-#if 0
-# include "compat/linkage-macros.hpp"
-# ifdef BUILD_TRACKER_PT
-# define OTR_PT_EXPORT OTR_GENERIC_EXPORT
-# else
-# define OTR_PT_EXPORT OTR_GENERIC_IMPORT
-# endif
-#else
-// static link
-# define OTR_PT_EXPORT
-#endif
diff --git a/tracker-pt/ftnoir_tracker_pt.cpp b/tracker-pt/ftnoir_tracker_pt.cpp
index 243fbd60..0f8495d9 100644
--- a/tracker-pt/ftnoir_tracker_pt.cpp
+++ b/tracker-pt/ftnoir_tracker_pt.cpp
@@ -6,36 +6,35 @@
* copyright notice and this permission notice appear in all copies.
*/
+#undef NDEBUG
#include "ftnoir_tracker_pt.h"
-#include "compat/camera-names.hpp"
-#include "compat/math-imports.hpp"
-
#include "pt-api.hpp"
+#include "cv/init.hpp"
+#include "video/video-widget.hpp"
+#include "compat/math-imports.hpp"
+#include "compat/check-visible.hpp"
+#include "compat/thread-name.hpp"
+#include "compat/qt-dpi.hpp"
-#include <cmath>
-
-#include <opencv2/imgproc.hpp>
-
+#include <cassert>
#include <QHBoxLayout>
#include <QDebug>
#include <QFile>
#include <QCoreApplication>
-using namespace types;
+using namespace options;
+
+namespace pt_impl {
-Tracker_PT::Tracker_PT(pointer<pt_runtime_traits> traits) :
+Tracker_PT::Tracker_PT(pointer<pt_runtime_traits> const& traits) :
traits { traits },
s { traits->get_module_name() },
point_extractor { traits->make_point_extractor() },
- camera { traits->make_camera() },
- frame { traits->make_frame() },
- preview_frame { traits->make_preview(preview_width, preview_height) }
+ frame { traits->make_frame() }
{
- cv::setBreakOnError(true);
+ opencv_init();
- connect(s.b.get(), SIGNAL(saving()), this, SLOT(maybe_reopen_camera()), Qt::DirectConnection);
- connect(&s.fov, SIGNAL(valueChanged(int)), this, SLOT(set_fov(int)), Qt::DirectConnection);
- set_fov(s.fov);
+ connect(&*s.b, &bundle_::saving, this, [this]{ reopen_camera_flag = true; }, Qt::DirectConnection);
}
Tracker_PT::~Tracker_PT()
@@ -43,111 +42,108 @@ Tracker_PT::~Tracker_PT()
requestInterruption();
wait();
- QMutexLocker l(&camera_mtx);
- camera->stop();
+ if (camera)
+ camera->stop();
}
-void Tracker_PT::run()
+bool Tracker_PT::check_camera()
{
- cv::setNumThreads(1);
+ if (reopen_camera_flag)
+ {
+ reopen_camera_flag = false;
+
+ camera = nullptr;
+ camera = traits->make_camera();
+ if (!camera || !camera->start(s))
+ return false;
+ }
+ assert(camera);
+ if (progn(bool x = true; return open_camera_dialog_flag.compare_exchange_strong(x, false);))
+ run_in_thread_sync(qApp->thread(), [this] { camera->show_camera_settings(); });
+ return true;
+}
-#ifdef PT_PERF_LOG
- QFile log_file(OPENTRACK_BASE_PATH + "/PointTrackerPerformance.txt");
- if (!log_file.open(QIODevice::WriteOnly | QIODevice::Text)) return;
- QTextStream log_stream(&log_file);
-#endif
+void Tracker_PT::run()
+{
+ portable::set_curthread_name("tracker/pt");
while(!isInterruptionRequested())
{
+ if (!check_camera())
+ break;
+
pt_camera_info info;
bool new_frame = false;
{
- QMutexLocker l(&camera_mtx);
-
- if (camera)
- std::tie(new_frame, info) = camera->get_frame(*frame);
+ camera->set_fov(s.fov);
+ std::tie(new_frame, info) = camera->get_frame(*frame);
}
if (new_frame)
{
- *preview_frame = *frame;
+ const bool preview_visible = check_is_visible();
- point_extractor->extract_points(*frame, *preview_frame, points);
- point_count = points.size();
+ if (preview_visible && !widget->fresh())
+ preview_frame->set_last_frame(*frame);
- const double fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y);
+ point_extractor->extract_points(*frame, *preview_frame, preview_visible && !widget->fresh(), points);
+ point_count.store(points.size(), std::memory_order_relaxed);
const bool success = points.size() >= PointModel::N_POINTS;
- if (success)
- {
- point_tracker.track(points,
- PointModel(s),
- info,
- s.dynamic_pose ? s.init_phase_timeout : 0);
- ever_success = true;
- }
+ Affine X_CM;
{
- Affine X_CM;
+ QMutexLocker l(&center_lock);
+
+ if (success)
{
- QMutexLocker l(&data_mtx);
- X_CM = point_tracker.pose();
+ int dynamic_pose_ms = s.dynamic_pose ? s.init_phase_timeout : 0;
+
+ point_tracker.track(points, PointModel(s), info, dynamic_pose_ms, filter, camera->deadzone_amount());
+ ever_success.store(true, std::memory_order_relaxed);
}
- // just copy pasted these lines from below
+ QMutexLocker l2(&data_lock);
+ X_CM = point_tracker.pose();
+ }
+
+ if (preview_visible && !widget->fresh())
+ {
+ const f fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y);
Affine X_MH(mat33::eye(), vec3(s.t_MH_x, s.t_MH_y, s.t_MH_z));
Affine X_GH = X_CM * X_MH;
vec3 p = X_GH.t; // head (center?) position in global space
- preview_frame->draw_head_center((p[0] * fx) / p[2], (p[1] * fx) / p[2]);
- }
-
- video_widget->update_image(preview_frame->get_bitmap());
+ if (p[2] > f(.1))
+ preview_frame->draw_head_center((p[0] * fx) / p[2], (p[1] * fx) / p[2]);
- {
- int w = -1, h = -1;
- video_widget->get_preview_size(w, h);
- if (w != preview_width || h != preview_height)
- {
- preview_width = w, preview_height = h;
- preview_frame = traits->make_preview(w, h);
- }
+ widget->update_image(preview_frame->get_bitmap());
}
}
}
- qDebug() << "pt: thread stopped";
-}
-
-bool Tracker_PT::maybe_reopen_camera()
-{
- QMutexLocker l(&camera_mtx);
-
- return camera->start(camera_name_to_index(s.camera_name),
- s.cam_fps, s.cam_res_x, s.cam_res_y);
-}
-
-void Tracker_PT::set_fov(int value)
-{
- QMutexLocker l(&camera_mtx);
- camera->set_fov(value);
}
module_status Tracker_PT::start_tracker(QFrame* video_frame)
{
- //video_frame->setAttribute(Qt::WA_NativeWindow);
+ {
+ auto camera = traits->make_camera();
+ if (!camera || !camera->start(s))
+ return error(tr("Failed to open camera '%1'").arg(s.camera_name));
+ }
- video_widget = std::make_unique<cv_video_widget>(video_frame);
+ widget = std::make_unique<video_widget>(video_frame);
layout = std::make_unique<QHBoxLayout>(video_frame);
layout->setContentsMargins(0, 0, 0, 0);
- layout->addWidget(video_widget.get());
- video_frame->setLayout(layout.get());
+ layout->addWidget(&*widget);
+ video_frame->setLayout(&*layout);
//video_widget->resize(video_frame->width(), video_frame->height());
video_frame->show();
- if (!maybe_reopen_camera())
- return { tr("Can't open camera") };
+ double dpi = screen_dpi(video_frame);
+ preview_frame = traits->make_preview(iround(preview_width * dpi),
+ iround(preview_height * dpi));
start(QThread::HighPriority);
@@ -156,76 +152,75 @@ module_status Tracker_PT::start_tracker(QFrame* video_frame)
void Tracker_PT::data(double *data)
{
- if (ever_success)
+ if (ever_success.load(std::memory_order_relaxed))
{
- Affine X_CM = pose();
+ Affine X_CM;
+ {
+ QMutexLocker l(&data_lock);
+ X_CM = point_tracker.pose();
+ }
Affine X_MH(mat33::eye(), vec3(s.t_MH_x, s.t_MH_y, s.t_MH_z));
- Affine X_GH = X_CM * X_MH;
+ Affine X_GH(X_CM * X_MH);
// translate rotation matrix from opengl (G) to roll-pitch-yaw (E) frame
// -z -> x, y -> z, x -> -y
mat33 R_EG(0, 0,-1,
-1, 0, 0,
0, 1, 0);
- mat33 R = R_EG * X_GH.R * R_EG.t();
+ mat33 R(R_EG * X_GH.R * R_EG.t());
// get translation(s)
const vec3& t = X_GH.t;
// extract rotation angles
- {
- f alpha, beta, gamma;
- beta = atan2( -R(2,0), sqrt(R(2,1)*R(2,1) + R(2,2)*R(2,2)) );
- alpha = atan2( R(1,0), R(0,0));
- gamma = atan2( R(2,1), R(2,2));
+ auto r00 = (double)R(0, 0);
+ auto r10 = (double)R(1,0), r20 = (double)R(2,0);
+ auto r21 = (double)R(2,1), r22 = (double)R(2,2);
-#if 0
- if (t[2] > 1e-4)
- {
- alpha += copysign(atan(t[0] / t[2]), t[0]);
- // pitch is skewed anyway due to only one focal length value
- //beta -= copysign(atan(t[1] / t[2]), t[1]);
- }
-#endif
+ double beta = atan2(-r20, sqrt(r21*r21 + r22*r22));
+ double alpha = atan2(r10, r00);
+ double gamma = atan2(r21, r22);
- data[Yaw] = rad2deg * alpha;
- data[Pitch] = -rad2deg * beta;
- data[Roll] = rad2deg * gamma;
- }
+ constexpr double rad2deg = 180/M_PI;
+
+ data[Yaw] = rad2deg * alpha;
+ data[Pitch] = -rad2deg * beta;
+ data[Roll] = rad2deg * gamma;
// convert to cm
- data[TX] = t[0] / 10;
- data[TY] = t[1] / 10;
- data[TZ] = t[2] / 10;
+ data[TX] = (double)t[0] / 10;
+ data[TY] = (double)t[1] / 10;
+ data[TZ] = (double)t[2] / 10;
}
}
bool Tracker_PT::center()
{
+ QMutexLocker l(&center_lock);
+
point_tracker.reset_state();
return false;
}
-Affine Tracker_PT::pose()
-{
- QMutexLocker l(&data_mtx);
-
- return point_tracker.pose();
-}
-
int Tracker_PT::get_n_points()
{
- return int(point_count);
+ return (int)point_count.load(std::memory_order_relaxed);
}
-bool Tracker_PT::get_cam_info(pt_camera_info* info)
+bool Tracker_PT::get_cam_info(pt_camera_info& info)
{
- QMutexLocker lock(&camera_mtx);
- bool ret;
+ bool ret = false;
- std::tie(ret, *info) = camera->get_info();
+ if (camera)
+ std::tie(ret, info) = camera->get_info();
return ret;
}
+Affine Tracker_PT::pose() const
+{
+ QMutexLocker l(&data_lock);
+ return point_tracker.pose();
+}
+} // ns pt_impl
diff --git a/tracker-pt/ftnoir_tracker_pt.h b/tracker-pt/ftnoir_tracker_pt.h
index 03812092..a793f94b 100644
--- a/tracker-pt/ftnoir_tracker_pt.h
+++ b/tracker-pt/ftnoir_tracker_pt.h
@@ -9,59 +9,47 @@
#pragma once
#include "api/plugin-api.hpp"
-
-#include "cv/numeric.hpp"
-
#include "pt-api.hpp"
#include "point_tracker.h"
-#include "cv/video-widget.hpp"
+#include "cv/numeric.hpp"
+#include "video/video-widget.hpp"
+#include "point-filter.hpp"
#include <atomic>
#include <memory>
#include <vector>
-#include <opencv2/core.hpp>
-
#include <QThread>
#include <QMutex>
#include <QLayout>
-#include <QTimer>
-class TrackerDialog_PT;
+namespace pt_impl {
-namespace pt_module {
+class TrackerDialog_PT;
-using namespace types;
+using namespace numeric_types;
-class Tracker_PT : public QThread, public ITracker
+struct Tracker_PT : QThread, ITracker
{
- Q_OBJECT
+ friend class TrackerDialog_PT;
- friend class ::TrackerDialog_PT;
+ template<typename t> using pointer = pt_pointer<t>;
- template<typename t>
- using pointer = typename pt_runtime_traits::pointer<t>;
-
-public:
- Tracker_PT(pointer<pt_runtime_traits> pt_runtime_traits);
+ explicit Tracker_PT(pointer<pt_runtime_traits> const& pt_runtime_traits);
~Tracker_PT() override;
module_status start_tracker(QFrame* parent_window) override;
void data(double* data) override;
bool center() override;
- Affine pose();
int get_n_points();
- bool get_cam_info(pt_camera_info* info);
-public slots:
- bool maybe_reopen_camera();
- void set_fov(int value);
-protected:
- void run() override;
+ [[nodiscard]] bool get_cam_info(pt_camera_info& info);
+ Affine pose() const;
+
private:
- pointer<pt_runtime_traits> traits;
+ void run() override;
+ [[nodiscard]] bool check_camera();
- QMutex camera_mtx;
- QMutex data_mtx;
+ pointer<pt_runtime_traits> traits;
PointTracker point_tracker;
@@ -74,17 +62,18 @@ private:
pointer<pt_point_extractor> point_extractor;
pointer<pt_camera> camera;
- pointer<cv_video_widget> video_widget;
+ pointer<video_widget> widget;
pointer<pt_frame> frame;
pointer<pt_preview> preview_frame;
- std::atomic<unsigned> point_count = 0;
+ std::atomic<unsigned> point_count { 0 };
std::atomic<bool> ever_success = false;
-
- static constexpr inline f rad2deg = f(180/M_PI);
- //static constexpr float deg2rad = float(M_PI/180);
+ std::atomic<bool> reopen_camera_flag = true;
+ std::atomic<bool> open_camera_dialog_flag = false;
+ mutable QMutex center_lock, data_lock;
+ point_filter filter{s};
};
} // ns pt_impl
-using pt_module::Tracker_PT;
+using Tracker_PT = pt_impl::Tracker_PT;
diff --git a/tracker-pt/ftnoir_tracker_pt_dialog.cpp b/tracker-pt/ftnoir_tracker_pt_dialog.cpp
index 5bd1a4c8..d67f79a7 100644
--- a/tracker-pt/ftnoir_tracker_pt_dialog.cpp
+++ b/tracker-pt/ftnoir_tracker_pt_dialog.cpp
@@ -7,11 +7,8 @@
*/
#include "ftnoir_tracker_pt_dialog.h"
-
#include "compat/math.hpp"
-#include "compat/camera-names.hpp"
-#include "cv/video-property-page.hpp"
-#include <opencv2/core.hpp>
+#include "video/camera.hpp"
#include <QString>
#include <QtGlobal>
@@ -19,22 +16,28 @@
using namespace options;
+static void init_resources() { Q_INIT_RESOURCE(tracker_pt_base); }
+
+namespace pt_impl {
+
TrackerDialog_PT::TrackerDialog_PT(const QString& module_name) :
s(module_name),
tracker(nullptr),
timer(this),
- trans_calib(1, 2, 0)
+ trans_calib(1, 2)
{
- Q_INIT_RESOURCE(tracker_pt_base);
+ init_resources();
ui.setupUi(this);
- ui.camdevice_combo->addItems(get_camera_names());
+ for (const QString& str : video::camera_names())
+ ui.camdevice_combo->addItem(str);
tie_setting(s.camera_name, ui.camdevice_combo);
tie_setting(s.cam_res_x, ui.res_x_spin);
tie_setting(s.cam_res_y, ui.res_y_spin);
tie_setting(s.cam_fps, ui.fps_spin);
+ tie_setting(s.use_mjpeg, ui.use_mjpeg);
tie_setting(s.threshold_slider, ui.threshold_slider);
@@ -88,13 +91,18 @@ TrackerDialog_PT::TrackerDialog_PT(const QString& module_name) :
poll_tracker_info_impl();
- connect(this, &TrackerDialog_PT::poll_tracker_info, this, &TrackerDialog_PT::poll_tracker_info_impl, Qt::DirectConnection);
-
constexpr pt_color_type color_types[] = {
- pt_color_average,
- pt_color_natural,
+ pt_color_bt709,
+ pt_color_hardware,
pt_color_red_only,
+ pt_color_green_only,
pt_color_blue_only,
+ pt_color_red_chromakey,
+ pt_color_green_chromakey,
+ pt_color_blue_chromakey,
+ pt_color_cyan_chromakey,
+ pt_color_yellow_chromakey,
+ pt_color_magenta_chromakey,
};
for (unsigned k = 0; k < std::size(color_types); k++)
@@ -102,14 +110,38 @@ TrackerDialog_PT::TrackerDialog_PT(const QString& module_name) :
tie_setting(s.blob_color, ui.blob_color);
+ tie_setting(s.chroma_key_strength, ui.chroma_key_strength_slider);
+ connect(&s.chroma_key_strength, value_::value_changed<slider_value>(), ui.chroma_key_strength_label,
+ [this] { ui.chroma_key_strength_label->setValue(*s.chroma_key_strength); });
+ ui.chroma_key_strength_label->setValue(*s.chroma_key_strength);
+
+ tie_setting(s.chroma_key_overexposed, ui.chroma_key_overexposed);
+ connect(ui.blob_color, &QComboBox::currentTextChanged, this, &TrackerDialog_PT::chroma_key_controls_enable);
+
+ chroma_key_controls_enable("");
+
tie_setting(s.threshold_slider, ui.threshold_value_display, [this](const slider_value& val) {
return threshold_display_text(int(val));
});
// refresh threshold display on auto-threshold checkbox state change
- tie_setting(s.auto_threshold,
- this,
- [this](bool) { s.threshold_slider.notify(); });
+ tie_setting(s.auto_threshold, this, [this](bool) { s.threshold_slider.notify_(); });
+
+ tie_setting(s.enable_point_filter, ui.enable_point_filter);
+ tie_setting(s.point_filter_coefficient, ui.point_filter_slider);
+ tie_setting(s.point_filter_limit, ui.point_filter_limit_slider);
+ connect(&s.point_filter_coefficient, value_::value_changed<slider_value>(),
+ ui.point_filter_label, [this] { ui.point_filter_label->setValue(*s.point_filter_coefficient); } );
+ connect(&s.point_filter_limit, value_::value_changed<slider_value>(), ui.point_filter_limit_label,
+ [this] { ui.point_filter_limit_label->setValue(*s.point_filter_limit); }, Qt::QueuedConnection);
+ ui.point_filter_label->setValue(*s.point_filter_coefficient);
+ ui.point_filter_limit_label->setValue(*s.point_filter_limit);
+
+ tie_setting(s.point_filter_deadzone, ui.point_filter_deadzone_slider);
+ ui.point_filter_deadzone_label->setValue(*s.point_filter_deadzone);
+
+ connect(&s.point_filter_deadzone, value_::value_changed<slider_value>(), ui.point_filter_deadzone_label,
+ [this] { ui.point_filter_deadzone_label->setValue(*s.point_filter_deadzone); }, Qt::QueuedConnection);
}
QString TrackerDialog_PT::threshold_display_text(int threshold_value)
@@ -122,12 +154,18 @@ QString TrackerDialog_PT::threshold_display_text(int threshold_value)
int w = s.cam_res_x, h = s.cam_res_y;
if (w * h <= 0)
- w = 640, h = 480;
+ {
+ w = 640;
+ h = 480;
+ }
- if (tracker && tracker->get_cam_info(&info) && info.res_x * info.res_y != 0)
- w = info.res_x, h = info.res_y;
+ if (tracker && tracker->get_cam_info(info) && info.res_x * info.res_y != 0)
+ {
+ w = info.res_x;
+ h = info.res_y;
+ }
- double value = pt_point_extractor::threshold_radius_value(w, h, threshold_value);
+ double value = (double)pt_point_extractor::threshold_radius_value(w, h, threshold_value);
return tr("LED radius %1 pixels").arg(value, 0, 'f', 2);
}
@@ -153,9 +191,7 @@ void TrackerDialog_PT::startstop_trans_calib(bool start)
calib_timer.stop();
qDebug() << "pt: stopping translation calibration";
{
- cv::Vec3f tmp;
- cv::Vec3i nsamples;
- std::tie(tmp, nsamples) = trans_calib.get_estimate();
+ auto [tmp, nsamples] = trans_calib.get_estimate();
s.t_MH_x = int(tmp[0]);
s.t_MH_y = int(tmp[1]);
s.t_MH_z = int(tmp[2]);
@@ -167,18 +203,16 @@ void TrackerDialog_PT::startstop_trans_calib(bool start)
// Don't bother counting roll samples. Roll calibration is hard enough
// that it's a hidden unsupported feature anyway.
- const QString sample_feedback = progn(
- if (nsamples[0] < min_yaw_samples)
- return tr("%1 yaw samples. Yaw more to %2 samples for stable calibration.")
- .arg(nsamples[0]).arg(min_yaw_samples);
- if (nsamples[1] < min_pitch_samples)
- return tr("%1 pitch samples. Pitch more to %2 samples for stable calibration.")
- .arg(nsamples[1]).arg(min_pitch_samples);
-
+ QString sample_feedback;
+ if (nsamples[0] < min_yaw_samples)
+ sample_feedback = tr("%1 yaw samples. Yaw more to %2 samples for stable calibration.").arg(nsamples[0]).arg(min_yaw_samples);
+ else if (nsamples[1] < min_pitch_samples)
+ sample_feedback = tr("%1 pitch samples. Pitch more to %2 samples for stable calibration.").arg(nsamples[1]).arg(min_pitch_samples);
+ else
+ {
const int nsamples_total = nsamples[0] + nsamples[1];
-
- return tr("%1 samples. Over %2, good!").arg(nsamples_total).arg(min_samples);
- );
+ sample_feedback = tr("%1 samples. Over %2, good!").arg(nsamples_total).arg(min_samples);
+ }
ui.sample_count_display->setText(sample_feedback);
}
@@ -186,18 +220,17 @@ void TrackerDialog_PT::startstop_trans_calib(bool start)
ui.tx_spin->setEnabled(!start);
ui.ty_spin->setEnabled(!start);
ui.tz_spin->setEnabled(!start);
- ui.tcalib_button->setText(progn(
- if (start)
- return tr("Stop calibration");
- else
- return tr("Start calibration");
- ));
+
+ if (start)
+ ui.tcalib_button->setText(tr("Stop calibration"));
+ else
+ ui.tcalib_button->setText(tr("Start calibration"));
}
void TrackerDialog_PT::poll_tracker_info_impl()
{
pt_camera_info info;
- if (tracker && tracker->get_cam_info(&info))
+ if (tracker && tracker->get_cam_info(info))
{
ui.caminfo_label->setText(tr("%1x%2 @ %3 FPS").arg(info.res_x).arg(info.res_y).arg(iround(info.fps)));
@@ -220,15 +253,23 @@ void TrackerDialog_PT::set_camera_settings_available(const QString& /* camera_na
void TrackerDialog_PT::show_camera_settings()
{
if (tracker)
- {
- QMutexLocker l(&tracker->camera_mtx);
- tracker->camera->show_camera_settings();
- }
+ tracker->open_camera_dialog_flag = true;
else
+ (void)video::show_dialog(s.camera_name);
+}
+
+void TrackerDialog_PT::chroma_key_controls_enable(const QString&)
+{
+ bool enabled = false;
+ QVariant data = ui.blob_color->currentData();
+ if (data.isValid())
{
- const int idx = camera_name_to_index(s.camera_name);
- video_property_page::show(idx);
+ pt_color_type blob_color = pt_color_type(data.toInt());
+ enabled = blob_color >= pt_color_red_chromakey && blob_color <= pt_color_magenta_chromakey;
}
+ ui.chroma_key_strength_slider->setEnabled(enabled);
+ ui.chroma_key_strength_label->setEnabled(enabled);
+ ui.chroma_key_overexposed->setEnabled(enabled);
}
void TrackerDialog_PT::trans_calib_step()
@@ -264,14 +305,26 @@ void TrackerDialog_PT::register_tracker(ITracker *t)
{
tracker = static_cast<Tracker_PT*>(t);
ui.tcalib_button->setEnabled(true);
- poll_tracker_info();
+ poll_tracker_info_impl();
timer.start();
}
void TrackerDialog_PT::unregister_tracker()
{
- tracker = NULL;
+ tracker = nullptr;
ui.tcalib_button->setEnabled(false);
- poll_tracker_info();
+ poll_tracker_info_impl();
timer.stop();
}
+
+void TrackerDialog_PT::set_buttons_visible(bool x)
+{
+ ui.buttonBox->setVisible(x);
+}
+
+void TrackerDialog_PT::reload()
+{
+ s.b->reload();
+}
+
+} // ns pt_impl
diff --git a/tracker-pt/ftnoir_tracker_pt_dialog.h b/tracker-pt/ftnoir_tracker_pt_dialog.h
index f36fe7b2..79cd91bd 100644
--- a/tracker-pt/ftnoir_tracker_pt_dialog.h
+++ b/tracker-pt/ftnoir_tracker_pt_dialog.h
@@ -12,11 +12,13 @@
#include "ftnoir_tracker_pt.h"
#include "tracker-pt/ui_FTNoIR_PT_Controls.h"
#include "cv/translation-calibrator.hpp"
-#include "cv/video-widget.hpp"
+#include "video/video-widget.hpp"
#include <QTimer>
#include <QMutex>
+namespace pt_impl {
+
class TrackerDialog_PT : public ITrackerDialog
{
Q_OBJECT
@@ -24,7 +26,10 @@ public:
TrackerDialog_PT(const QString& module_name);
void register_tracker(ITracker *tracker) override;
void unregister_tracker() override;
- void save();
+ bool embeddable() noexcept override { return true; }
+ void set_buttons_visible(bool x) override;
+ void save() override;
+ void reload() override;
public slots:
void doOK();
void doCancel();
@@ -34,8 +39,8 @@ public slots:
void poll_tracker_info_impl();
void set_camera_settings_available(const QString& camera_name);
void show_camera_settings();
-signals:
- void poll_tracker_info();
+ void chroma_key_controls_enable(const QString&);
+
protected:
QString threshold_display_text(int threshold_value);
@@ -47,3 +52,7 @@ protected:
Ui::UICPTClientControls ui;
};
+
+} // ns pt_impl
+
+using TrackerDialog_PT = pt_impl::TrackerDialog_PT;
diff --git a/tracker-pt/lang/de_DE.ts b/tracker-pt/lang/de_DE.ts
new file mode 100644
index 00000000..30ab42c2
--- /dev/null
+++ b/tracker-pt/lang/de_DE.ts
@@ -0,0 +1,378 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>UICPTClientControls</name>
+ <message>
+ <source>PointTracker Settings</source>
+ <translation>PointTracker-Einstellungen</translation>
+ </message>
+ <message>
+ <source>Camera</source>
+ <translation>Kamera</translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation>Kamera-Einstellungen</translation>
+ </message>
+ <message>
+ <source>This should be 56° or 76° for the PS3 Eye, dependent upon the physical lens setting. It&apos;s only neccessary to get position correspond to real-world values.</source>
+ <translation>Für die PS3 Eye sollte dies 56° oder 76° sein, abhängig von der Einstellung der physikalischen Linse. Dies wird nur benutzt, um die Position aus den Welt-Koordinaten zur ermitteln.</translation>
+ </message>
+ <message>
+ <source>Diagonal field of view</source>
+ <translation>Diagonales Sichtfeld</translation>
+ </message>
+ <message>
+ <source>Dynamic pose (for caps only, never clips)</source>
+ <translation>Dynamische Pose (nur für Hüte, niemals Sticker)</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;For LEDs, &apos;Natural&apos; is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;‚Natürlich‘ ist, dank des optimierten SIMD-Codes, die schnellste Methode für LEDs. Mit dem Farbschlüssel kann man normale farbige Papierstücke tracken.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>Color channels used</source>
+ <translation>Benutzte Farbkanäle</translation>
+ </message>
+ <message>
+ <source>Open</source>
+ <translation>Öffnen</translation>
+ </message>
+ <message>
+ <source>Enable MJPEG compression for high-speed cameras other than the PS3 Eye. Windows only.</source>
+ <translation>Aktiviert MJPEG-Kompression für Hochgeschwindigkeitskameras, PS3 Eye ausgenommen. Nur für Windows.</translation>
+ </message>
+ <message>
+ <source>MJPEG compression</source>
+ <translation>MJPEG-Kompression</translation>
+ </message>
+ <message>
+ <source>FPS</source>
+ <translation>FPS</translation>
+ </message>
+ <message>
+ <source>Width</source>
+ <translation>Breite</translation>
+ </message>
+ <message>
+ <source>Chroma key includes overexposed pixels</source>
+ <translation>Chroma-Key enthält überbelichtete Pixel</translation>
+ </message>
+ <message>
+ <source>Camera settings (when available)</source>
+ <translation>Kamera-Einstellungen (falls verfügbar)</translation>
+ </message>
+ <message>
+ <source> ms</source>
+ <translation> ms</translation>
+ </message>
+ <message>
+ <source>Desired capture width</source>
+ <translation>Angestrebte Aufnahmebreite</translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation> px</translation>
+ </message>
+ <message>
+ <source>Dynamic pose timeout</source>
+ <translation>Dynamisches Pose-Timeout</translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation>Gerät</translation>
+ </message>
+ <message>
+ <source>Height</source>
+ <translation>Höhe</translation>
+ </message>
+ <message>
+ <source>Desired capture framerate</source>
+ <translation>Angestrebte Aufnahme-Bildrate</translation>
+ </message>
+ <message>
+ <source> Hz</source>
+ <translation> Hz</translation>
+ </message>
+ <message>
+ <source>Grayscale BT.709</source>
+ <translation>Graustufen BT.709</translation>
+ </message>
+ <message>
+ <source>Grayscale (from hardware)</source>
+ <translation>Graustufen (gemäß Hardware)</translation>
+ </message>
+ <message>
+ <source>Red only</source>
+ <translation>nur Rot</translation>
+ </message>
+ <message>
+ <source>Green only</source>
+ <translation>nur Grün</translation>
+ </message>
+ <message>
+ <source>Blue only</source>
+ <translation>nur Blau</translation>
+ </message>
+ <message>
+ <source>Red chroma key</source>
+ <translation>Roter Chroma-Key</translation>
+ </message>
+ <message>
+ <source>Green chroma key</source>
+ <translation>Grüner Chroma-Key</translation>
+ </message>
+ <message>
+ <source>Blue chroma key</source>
+ <translation>Blauer Chroma-Key</translation>
+ </message>
+ <message>
+ <source>Cyan chroma key</source>
+ <translation>Cyan-Chroma-Key</translation>
+ </message>
+ <message>
+ <source>Yellow chroma key</source>
+ <translation>Gelber Chroma-Key</translation>
+ </message>
+ <message>
+ <source>Magenta chroma key</source>
+ <translation>Magenta-Chroma-Key</translation>
+ </message>
+ <message>
+ <source>°</source>
+ <translation>°</translation>
+ </message>
+ <message>
+ <source>Desired capture height</source>
+ <translation>Angestrebte Aufnahmehöhe</translation>
+ </message>
+ <message>
+ <source>Chroma key strength</source>
+ <translation>Chroma-Key-Stärke</translation>
+ </message>
+ <message>
+ <source>Point extraction</source>
+ <translation>Punktextraktion</translation>
+ </message>
+ <message>
+ <source>Threshold</source>
+ <translation>Schwelle</translation>
+ </message>
+ <message>
+ <source>Set minimum size to avoid small stray lights from being treated as points.</source>
+ <translation>Setzt die Mindestgröße, um zu verhindern, dass kleine Streulichter als Punkte erkannt werden.</translation>
+ </message>
+ <message>
+ <source>Min size</source>
+ <translation>Mindestgröße</translation>
+ </message>
+ <message>
+ <source>Max size</source>
+ <translation>Maximale Größe</translation>
+ </message>
+ <message>
+ <source>Intensity threshold for point extraction</source>
+ <translation>Intensitätsschwelle für die Punktextraktion</translation>
+ </message>
+ <message>
+ <source>Enable, slider sets point size</source>
+ <translation>Aktivieren, Regler setzt die Punktgröße</translation>
+ </message>
+ <message>
+ <source>Track dependent on point size and not absolute brightness. This may allow more stable tracking.</source>
+ <translation>Tracking abhängig von der Punktgröße anstelle der absoluten Helligkeit durchführen. Dies kann stabileres Tracking ermöglichen.</translation>
+ </message>
+ <message>
+ <source>Automatic threshold</source>
+ <translation>Automatische Schwelle</translation>
+ </message>
+ <message>
+ <source>Maximum point diameter</source>
+ <translation>Maximaler Punktdurchmesser</translation>
+ </message>
+ <message>
+ <source>Value</source>
+ <translation>Wert</translation>
+ </message>
+ <message>
+ <source>Minimum point diameter</source>
+ <translation>Minimaler Punktdurchmesser</translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation>Modell</translation>
+ </message>
+ <message>
+ <source>Clip</source>
+ <translation>Sticker</translation>
+ </message>
+ <message>
+ <source>Model Dimensions</source>
+ <translation>Modellabmessungen</translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation> mm</translation>
+ </message>
+ <message>
+ <source>Side</source>
+ <translation>Seitlich</translation>
+ </message>
+ <message>
+ <source>Front</source>
+ <translation>Vorne</translation>
+ </message>
+ <message>
+ <source>Cap</source>
+ <translation>Hut</translation>
+ </message>
+ <message>
+ <source>Custom</source>
+ <translation>Benutzerdefiniert</translation>
+ </message>
+ <message>
+ <source>z:</source>
+ <translation>z:</translation>
+ </message>
+ <message>
+ <source>x:</source>
+ <translation>x:</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Location of the two remaining model points&lt;br/&gt;with respect to the reference point in default pose&lt;/p&gt;&lt;p&gt;Use any units you want, not necessarily centimeters.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ort der verbleibenden zwei Modellpunkte&lt;br/&gt;unter Berücksichtigung der Referenzpunkte der Standard-Pose&lt;/p&gt;&lt;p&gt;Dies können beliebige Einheiten sein, nicht zwingend Zentimeter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>y:</source>
+ <translation>y:</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;3&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;3&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;2&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;2&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>Model position</source>
+ <translation>Modell-Position</translation>
+ </message>
+ <message>
+ <source>Use only yaw and pitch while calibrating.
+Don&apos;t roll or change position.</source>
+ <translation>Bitte nur gieren oder nicken während der Kalibrierung.
+Bitte nicht rollen oder die Position ändern.</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation>Kalibrierung starten</translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation>Filter</translation>
+ </message>
+ <message>
+ <source>Point filter</source>
+ <translation>Punktfilter</translation>
+ </message>
+ <message>
+ <source>Limit</source>
+ <translation>Grenze</translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation>Totbereich</translation>
+ </message>
+ <message>
+ <source>Filter point centers prior to pose estimation.</source>
+ <translation>Punktmitten vor der Posen-Abschätzung filtern.</translation>
+ </message>
+ <message>
+ <source>Enable</source>
+ <translation>Einschalten</translation>
+ </message>
+ <message>
+ <source>About</source>
+ <translation>Über</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;FTNoIR PointTracker Plugin&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Patrick Ruoff&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://ftnoirpt.sourceforge.net/&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#0000ff;&quot;&gt;Manual (external)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;FTNoIR PointTracker Plugin&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;von Patrick Ruoff&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://ftnoirpt.sourceforge.net/&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#0000ff;&quot;&gt;Anleitung (extern)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>Status</source>
+ <translation>Status</translation>
+ </message>
+ <message>
+ <source>Extracted Points:</source>
+ <translation>Extrahierte Punkte:</translation>
+ </message>
+ <message>
+ <source>Camera Info:</source>
+ <translation>Kamera-Info:</translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::TrackerDialog_PT</name>
+ <message>
+ <source>Brightness %1/255</source>
+ <translation>Helligkeit %1/255</translation>
+ </message>
+ <message>
+ <source>LED radius %1 pixels</source>
+ <translation>LED-Radius %1 Pixel</translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation>%1 Gieren-Proben. Weiterhin gieren bis %2 Proben für eine stabile Kalibrierung.</translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation>%1 Nicken-Proben. Weiterhin nicken bis %2 Proben für eine stabile Kalibrierung.</translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation>%1 Proben. Mehr als %2, gut!</translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation>Kalibrierung stoppen</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation>Kalibrierung starten</translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS</source>
+ <translation>%1x%2 @ %3 FPS</translation>
+ </message>
+ <message>
+ <source>%1 OK!</source>
+ <translation>%1 OKAY!</translation>
+ </message>
+ <message>
+ <source>%1 BAD!</source>
+ <translation>%1 SCHLECHT!</translation>
+ </message>
+ <message>
+ <source>Tracker offline</source>
+ <translation>Tracker offline</translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::Tracker_PT</name>
+ <message>
+ <source>Failed to open camera &apos;%1&apos;</source>
+ <translation>Öffnen der Kamera ‚%1‘ fehlgeschlagen</translation>
+ </message>
+</context>
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>PointTracker 1.1</source>
+ <translation>PointTracker 1.1</translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-pt/lang/nl_NL.ts b/tracker-pt/lang/nl_NL.ts
index 34f88c88..fc44b0f1 100644
--- a/tracker-pt/lang/nl_NL.ts
+++ b/tracker-pt/lang/nl_NL.ts
@@ -2,53 +2,6 @@
<!DOCTYPE TS>
<TS version="2.1" language="nl_NL">
<context>
- <name>TrackerDialog_PT</name>
- <message>
- <source>Brightness %1/255</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>LED radius %1 pixels</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 samples. Over %2, good!</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Stop calibration</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Start calibration</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1x%2 @ %3 FPS</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 OK!</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 BAD!</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Tracker offline</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-<context>
<name>UICPTClientControls</name>
<message>
<source>PointTracker Settings</source>
@@ -155,14 +108,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Average</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Natural</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Red only</source>
<translation type="unfinished"></translation>
</message>
@@ -275,11 +220,157 @@ Don&apos;t roll or change position.</source>
<source>Camera Info:</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Green only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Red chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Green chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Blue chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Cyan chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yellow chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Magenta chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>This should be 56° or 76° for the PS3 Eye, dependent upon the physical lens setting. It&apos;s only neccessary to get position correspond to real-world values.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Enable MJPEG compression for high-speed cameras other than the PS3 Eye. Windows only.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Set minimum size to avoid small stray lights from being treated as points.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Track dependent on point size and not absolute brightness. This may allow more stable tracking.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;For LEDs, &apos;Natural&apos; is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>MJPEG compression</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Point filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter point centers prior to pose estimation.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Enable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Limit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Grayscale BT.709</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Grayscale (from hardware)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Chroma key includes overexposed pixels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Chroma key strength</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::TrackerDialog_PT</name>
+ <message>
+ <source>Brightness %1/255</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>LED radius %1 pixels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 OK!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 BAD!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker offline</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::Tracker_PT</name>
+ <message>
+ <source>Failed to open camera &apos;%1&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
- <name>pt_module::Tracker_PT</name>
+ <name>pt_module::metadata_pt</name>
<message>
- <source>Can&apos;t open camera</source>
+ <source>PointTracker 1.1</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-pt/lang/ru_RU.ts b/tracker-pt/lang/ru_RU.ts
index 63b4847a..7ff4657e 100644
--- a/tracker-pt/lang/ru_RU.ts
+++ b/tracker-pt/lang/ru_RU.ts
@@ -2,53 +2,6 @@
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
<context>
- <name>TrackerDialog_PT</name>
- <message>
- <source>%1 samples. Over %2, good!</source>
- <translation>Получено %1 образца(-ов). Больше %2, отлично!!</translation>
- </message>
- <message>
- <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
- <translation>По оÑи YAW выполнено: %1 замер(а/ов). Ð”Ð»Ñ Ñтабильного результата необходимо не меньше %2</translation>
- </message>
- <message>
- <source>Brightness %1/255</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>LED radius %1 pixels</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
- <translation>По оÑи Pitch выполнено: %1 замер(а/ов). Ð”Ð»Ñ Ñтабильного результата необходимо не меньше %2</translation>
- </message>
- <message>
- <source>Stop calibration</source>
- <translation>ОÑтановить калибровку</translation>
- </message>
- <message>
- <source>Start calibration</source>
- <translation>Ðачать калибровку</translation>
- </message>
- <message>
- <source>%1x%2 @ %3 FPS</source>
- <translation></translation>
- </message>
- <message>
- <source>%1 OK!</source>
- <translation></translation>
- </message>
- <message>
- <source>%1 BAD!</source>
- <translation></translation>
- </message>
- <message>
- <source>Tracker offline</source>
- <translation>ОтÑлеживание отключено</translation>
- </message>
-</context>
-<context>
<name>UICPTClientControls</name>
<message>
<source>PointTracker Settings</source>
@@ -159,14 +112,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Average</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Natural</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Red only</source>
<translation type="unfinished"></translation>
</message>
@@ -280,11 +225,157 @@ ROLL или X/Y-ÑмещениÑ.</translation>
<source>Camera Info:</source>
<translation>Параметры камеры:</translation>
</message>
+ <message>
+ <source>Green only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Red chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Green chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Blue chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Cyan chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yellow chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Magenta chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>This should be 56° or 76° for the PS3 Eye, dependent upon the physical lens setting. It&apos;s only neccessary to get position correspond to real-world values.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Enable MJPEG compression for high-speed cameras other than the PS3 Eye. Windows only.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Set minimum size to avoid small stray lights from being treated as points.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Track dependent on point size and not absolute brightness. This may allow more stable tracking.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;For LEDs, &apos;Natural&apos; is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>MJPEG compression</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Point filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter point centers prior to pose estimation.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Enable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Limit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Grayscale BT.709</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Grayscale (from hardware)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Chroma key includes overexposed pixels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Chroma key strength</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::TrackerDialog_PT</name>
+ <message>
+ <source>Brightness %1/255</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>LED radius %1 pixels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation type="unfinished">По оÑи YAW выполнено: %1 замер(а/ов). Ð”Ð»Ñ Ñтабильного результата необходимо не меньше %2</translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation type="unfinished">По оÑи Pitch выполнено: %1 замер(а/ов). Ð”Ð»Ñ Ñтабильного результата необходимо не меньше %2</translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation type="unfinished">Получено %1 образца(-ов). Больше %2, отлично!!</translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation type="unfinished">ОÑтановить калибровку</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished">Ðачать калибровку</translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 OK!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 BAD!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker offline</source>
+ <translation type="unfinished">ОтÑлеживание отключено</translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::Tracker_PT</name>
+ <message>
+ <source>Failed to open camera &apos;%1&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
- <name>pt_module::Tracker_PT</name>
+ <name>pt_module::metadata_pt</name>
<message>
- <source>Can&apos;t open camera</source>
+ <source>PointTracker 1.1</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-pt/lang/stub.ts b/tracker-pt/lang/stub.ts
index e83487a9..3dbe208d 100644
--- a/tracker-pt/lang/stub.ts
+++ b/tracker-pt/lang/stub.ts
@@ -2,53 +2,6 @@
<!DOCTYPE TS>
<TS version="2.1">
<context>
- <name>TrackerDialog_PT</name>
- <message>
- <source>Brightness %1/255</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>LED radius %1 pixels</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 samples. Over %2, good!</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Stop calibration</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Start calibration</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1x%2 @ %3 FPS</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 OK!</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 BAD!</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Tracker offline</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-<context>
<name>UICPTClientControls</name>
<message>
<source>PointTracker Settings</source>
@@ -155,14 +108,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Average</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Natural</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Red only</source>
<translation type="unfinished"></translation>
</message>
@@ -275,11 +220,157 @@ Don&apos;t roll or change position.</source>
<source>Camera Info:</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Green only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Red chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Green chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Blue chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Cyan chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yellow chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Magenta chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>This should be 56° or 76° for the PS3 Eye, dependent upon the physical lens setting. It&apos;s only neccessary to get position correspond to real-world values.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Enable MJPEG compression for high-speed cameras other than the PS3 Eye. Windows only.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Set minimum size to avoid small stray lights from being treated as points.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Track dependent on point size and not absolute brightness. This may allow more stable tracking.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;For LEDs, &apos;Natural&apos; is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>MJPEG compression</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Point filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter point centers prior to pose estimation.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Enable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Limit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Grayscale BT.709</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Grayscale (from hardware)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Chroma key includes overexposed pixels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Chroma key strength</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::TrackerDialog_PT</name>
+ <message>
+ <source>Brightness %1/255</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>LED radius %1 pixels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 OK!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 BAD!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker offline</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::Tracker_PT</name>
+ <message>
+ <source>Failed to open camera &apos;%1&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
- <name>pt_module::Tracker_PT</name>
+ <name>pt_module::metadata_pt</name>
<message>
- <source>Can&apos;t open camera</source>
+ <source>PointTracker 1.1</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-pt/lang/zh_CN.ts b/tracker-pt/lang/zh_CN.ts
index d6ce141e..3519d719 100644
--- a/tracker-pt/lang/zh_CN.ts
+++ b/tracker-pt/lang/zh_CN.ts
@@ -2,53 +2,6 @@
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
- <name>TrackerDialog_PT</name>
- <message>
- <source>Brightness %1/255</source>
- <translation>亮度 %1/255</translation>
- </message>
- <message>
- <source>LED radius %1 pixels</source>
- <translation>å…‰æºåŠå¾„ %1 åƒç´ </translation>
- </message>
- <message>
- <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>%1 samples. Over %2, good!</source>
- <translation>%1 样本。%2 正常</translation>
- </message>
- <message>
- <source>Stop calibration</source>
- <translation>åœæ­¢æ ¡å‡†</translation>
- </message>
- <message>
- <source>Start calibration</source>
- <translation>开始校准</translation>
- </message>
- <message>
- <source>%1x%2 @ %3 FPS</source>
- <translation>%1x%2 @ %3 帧</translation>
- </message>
- <message>
- <source>%1 OK!</source>
- <translation>%1 正常</translation>
- </message>
- <message>
- <source>%1 BAD!</source>
- <translation>%1 异常</translation>
- </message>
- <message>
- <source>Tracker offline</source>
- <translation>跟踪器脱机</translation>
- </message>
-</context>
-<context>
<name>UICPTClientControls</name>
<message>
<source>PointTracker Settings</source>
@@ -247,14 +200,6 @@
<translation type="unfinished"></translation>
</message>
<message>
- <source>Average</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Natural</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
<source>Red only</source>
<translation type="unfinished"></translation>
</message>
@@ -275,12 +220,158 @@
Don&apos;t roll or change position.</source>
<translation>用pitchå’Œyaw校准。ä¸è¦rollæˆ–è€…å˜æ¢ä½ç½®</translation>
</message>
+ <message>
+ <source>Green only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Red chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Green chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Blue chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Cyan chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yellow chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Magenta chroma key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>This should be 56° or 76° for the PS3 Eye, dependent upon the physical lens setting. It&apos;s only neccessary to get position correspond to real-world values.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Enable MJPEG compression for high-speed cameras other than the PS3 Eye. Windows only.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Set minimum size to avoid small stray lights from being treated as points.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Track dependent on point size and not absolute brightness. This may allow more stable tracking.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;For LEDs, &apos;Natural&apos; is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>MJPEG compression</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Point filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter point centers prior to pose estimation.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Enable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Limit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Grayscale BT.709</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Grayscale (from hardware)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Chroma key includes overexposed pixels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Chroma key strength</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::TrackerDialog_PT</name>
+ <message>
+ <source>Brightness %1/255</source>
+ <translation type="unfinished">亮度 %1/255</translation>
+ </message>
+ <message>
+ <source>LED radius %1 pixels</source>
+ <translation type="unfinished">å…‰æºåŠå¾„ %1 åƒç´ </translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation type="unfinished">%1 样本。%2 正常</translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation type="unfinished">åœæ­¢æ ¡å‡†</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished">开始校准</translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS</source>
+ <translation type="unfinished">%1x%2 @ %3 帧</translation>
+ </message>
+ <message>
+ <source>%1 OK!</source>
+ <translation type="unfinished">%1 正常</translation>
+ </message>
+ <message>
+ <source>%1 BAD!</source>
+ <translation type="unfinished">%1 异常</translation>
+ </message>
+ <message>
+ <source>Tracker offline</source>
+ <translation type="unfinished">跟踪器脱机</translation>
+ </message>
</context>
<context>
- <name>pt_module::Tracker_PT</name>
+ <name>pt_impl::Tracker_PT</name>
<message>
- <source>Can&apos;t open camera</source>
- <translation type="unfinished">无法打开摄åƒå¤´</translation>
+ <source>Failed to open camera &apos;%1&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>PointTracker 1.1</source>
+ <translation type="unfinished"></translation>
</message>
</context>
</TS>
diff --git a/tracker-pt/module/CMakeLists.txt b/tracker-pt/module/CMakeLists.txt
index 1d1b4458..b7fc974f 100644
--- a/tracker-pt/module/CMakeLists.txt
+++ b/tracker-pt/module/CMakeLists.txt
@@ -1,6 +1,10 @@
-find_package(OpenCV 3.0 QUIET)
+include(opentrack-opencv)
+find_package(OpenCV QUIET)
if(OpenCV_FOUND)
+ foreach(k core imgproc)
+ otr_install_lib("opencv_${k}" "${opentrack-libexec}")
+ endforeach()
otr_module(tracker-pt)
- target_link_libraries(opentrack-tracker-pt opentrack-tracker-pt-base)
- target_include_directories(opentrack-tracker-pt PRIVATE "${CMAKE_SOURCE_DIR}/tracker-pt")
+ target_link_libraries(${self} opentrack-video opencv_imgproc opentrack-tracker-pt-base)
+ target_include_directories(${self} PUBLIC "${CMAKE_SOURCE_DIR}/tracker-pt")
endif()
diff --git a/tracker-pt/module/camera.cpp b/tracker-pt/module/camera.cpp
index 9c62e8a3..1beba474 100644
--- a/tracker-pt/module/camera.cpp
+++ b/tracker-pt/module/camera.cpp
@@ -7,16 +7,9 @@
#include "camera.h"
#include "frame.hpp"
+#include <opencv2/core/mat.hpp>
-#include "compat/sleep.hpp"
-#include "compat/camera-names.hpp"
-#include "compat/math-imports.hpp"
-
-#include <opencv2/imgproc.hpp>
-
-#include "cv/video-property-page.hpp"
-
-using namespace pt_module;
+namespace pt_module {
Camera::Camera(const QString& module_name) : s { module_name }
{
@@ -24,22 +17,18 @@ Camera::Camera(const QString& module_name) : s { module_name }
QString Camera::get_desired_name() const
{
- return desired_name;
+ return cam_desired.name;
}
QString Camera::get_active_name() const
{
- return active_name;
+ return cam_info.name;
}
void Camera::show_camera_settings()
{
- const int idx = camera_name_to_index(s.camera_name);
-
- if (cap && cap->isOpened())
- video_property_page::show_from_capture(*cap, idx);
- else
- video_property_page::show(idx);
+ if (cap)
+ (void)cap->show_dialog();
}
Camera::result Camera::get_info() const
@@ -54,16 +43,16 @@ Camera::result Camera::get_frame(pt_frame& frame_)
{
cv::Mat& frame = frame_.as<Frame>()->mat;
- const bool new_frame = _get_frame(frame);
+ const bool new_frame = get_frame_(frame);
if (new_frame)
{
- const double dt = t.elapsed_seconds();
+ const f dt = (f)t.elapsed_seconds();
t.start();
// measure fps of valid frames
- constexpr double RC = .1; // seconds
- const double alpha = dt/(dt + RC);
+ constexpr f RC = f{1}/10; // seconds
+ const f alpha = dt/(dt + RC);
if (dt_mean < dt_eps)
dt_mean = dt;
@@ -75,64 +64,69 @@ Camera::result Camera::get_frame(pt_frame& frame_)
cam_info.res_y = frame.rows;
cam_info.fov = fov;
- return result(true, cam_info);
+ return { true, cam_info };
}
else
- return result(false, pt_camera_info());
+ return { false, {} };
}
-bool Camera::start(int idx, int fps, int res_x, int res_y)
+bool Camera::start(const pt_settings& s)
{
- if (idx >= 0 && fps >= 0 && res_x >= 0 && res_y >= 0)
+ int fps = s.cam_fps, res_x = s.cam_res_x, res_y = s.cam_res_y;
+ QString name = s.camera_name;
+ bool use_mjpeg = s.use_mjpeg;
+
+ if (fps >= 0 && res_x >= 0 && res_y >= 0)
{
- if (cam_desired.idx != idx ||
- cam_desired.fps != fps ||
+ if (cam_desired.name != name ||
+ (int)cam_desired.fps != fps ||
cam_desired.res_x != res_x ||
cam_desired.res_y != res_y ||
- !cap || !cap->isOpened() || !cap->grab())
+ cam_desired.use_mjpeg != use_mjpeg ||
+ !cap || !cap->is_open())
{
stop();
- desired_name = get_camera_names().value(idx);
- cam_desired.idx = idx;
- cam_desired.fps = fps;
+ cam_desired.name = name;
+ cam_desired.fps = (f)fps;
cam_desired.res_x = res_x;
cam_desired.res_y = res_y;
cam_desired.fov = fov;
+ cam_desired.use_mjpeg = use_mjpeg;
- cap = camera_ptr(new cv::VideoCapture(cam_desired.idx));
-
- if (cam_desired.res_x)
- cap->set(cv::CAP_PROP_FRAME_WIDTH, cam_desired.res_x);
- if (cam_desired.res_y)
- cap->set(cv::CAP_PROP_FRAME_HEIGHT, cam_desired.res_y);
- if (cam_desired.fps)
- cap->set(cv::CAP_PROP_FPS, cam_desired.fps);
-
- if (cap->isOpened())
- {
- cam_info = pt_camera_info();
- active_name = QString();
- cam_info.idx = idx;
- dt_mean = 0;
- active_name = desired_name;
-
- cv::Mat tmp;
-
- if (_get_frame(tmp))
- {
- t.start();
- return true;
- }
- }
-
- cap = nullptr;
- return false;
- }
+ cap = video::make_camera(name);
+
+ if (!cap)
+ goto fail;
+
+ camera::info info {};
+ info.fps = fps;
+ info.width = res_x;
+ info.height = res_y;
+ info.use_mjpeg = use_mjpeg;
+ info.num_channels = s.blob_color == pt_color_hardware ? 1 : 3;
+
+ if (!cap->start(info))
+ goto fail;
- return true;
+ cam_info = pt_camera_info();
+ cam_info.name = name;
+ cam_info.use_mjpeg = use_mjpeg;
+ cam_info.fov = (f)s.fov;
+ dt_mean = 0;
+
+ cv::Mat tmp;
+
+ if (!get_frame_(tmp))
+ goto fail;
+
+ t.start();
+ }
}
+ return true;
+
+fail:
stop();
return false;
}
@@ -140,33 +134,26 @@ bool Camera::start(int idx, int fps, int res_x, int res_y)
void Camera::stop()
{
cap = nullptr;
- desired_name = QString();
- active_name = QString();
- cam_info = pt_camera_info();
- cam_desired = pt_camera_info();
+ cam_info = {};
+ cam_desired = {};
}
-bool Camera::_get_frame(cv::Mat& frame)
+bool Camera::get_frame_(cv::Mat& img)
{
- if (cap && cap->isOpened())
+ if (cap && cap->is_open())
{
- for (int i = 0; i < 5; i++)
+ auto [ frame, ret ] = cap->get_frame();
+ if (ret)
{
- if (cap->read(frame))
- return true;
- portable::sleep(1);
+ int stride = frame.stride;
+ if (stride == 0)
+ stride = cv::Mat::AUTO_STEP;
+ img = cv::Mat(frame.height, frame.width, CV_8UC(frame.channels), (void*)frame.data, (size_t)stride);
+ return true;
}
}
- return false;
-}
-void Camera::camera_deleter::operator()(cv::VideoCapture* cap)
-{
- if (cap)
- {
- if (cap->isOpened())
- cap->release();
- delete cap;
- }
+ return false;
}
+} // ns pt_module
diff --git a/tracker-pt/module/camera.h b/tracker-pt/module/camera.h
index 79e3dca0..e4772178 100644
--- a/tracker-pt/module/camera.h
+++ b/tracker-pt/module/camera.h
@@ -8,15 +8,10 @@
#pragma once
#include "pt-api.hpp"
-
#include "compat/timer.hpp"
+#include "video/camera.hpp"
-#include <functional>
#include <memory>
-#include <tuple>
-
-#include <opencv2/core.hpp>
-#include <opencv2/videoio.hpp>
#include <QString>
@@ -26,7 +21,7 @@ struct Camera final : pt_camera
{
Camera(const QString& module_name);
- bool start(int idx, int fps, int res_x, int res_y) override;
+ bool start(const pt_settings& s) override;
void stop() override;
result get_frame(pt_frame& Frame) override;
@@ -36,30 +31,23 @@ struct Camera final : pt_camera
QString get_desired_name() const override;
QString get_active_name() const override;
- void set_fov(double value) override { fov = value; }
+ void set_fov(f value) override { fov = value; }
void show_camera_settings() override;
private:
- warn_result_unused bool _get_frame(cv::Mat& Frame);
+ using camera = video::impl::camera;
+
+ [[nodiscard]] bool get_frame_(cv::Mat& frame);
- double dt_mean = 0, fov = 30;
+ f dt_mean = 0, fov = 30;
Timer t;
pt_camera_info cam_info;
pt_camera_info cam_desired;
- QString desired_name, active_name;
-
- struct camera_deleter final
- {
- void operator()(cv::VideoCapture* cap);
- };
-
- using camera_ptr = std::unique_ptr<cv::VideoCapture, camera_deleter>;
-
- camera_ptr cap;
+ std::unique_ptr<camera> cap;
pt_settings s;
- static constexpr inline double dt_eps = 1./384;
+ static constexpr f dt_eps = f{1}/256;
};
} // ns pt_module
diff --git a/tracker-pt/module/export.hpp b/tracker-pt/module/export.hpp
new file mode 100644
index 00000000..a733c9fe
--- /dev/null
+++ b/tracker-pt/module/export.hpp
@@ -0,0 +1,11 @@
+// generates export.hpp for each module from compat/linkage.hpp
+
+#pragma once
+
+#include "compat/linkage-macros.hpp"
+
+#ifdef BUILD_TRACKER_PT
+# define OTR_PT_EXPORT OTR_GENERIC_EXPORT
+#else
+# define OTR_PT_EXPORT OTR_GENERIC_IMPORT
+#endif
diff --git a/tracker-pt/module/frame.cpp b/tracker-pt/module/frame.cpp
index e403af07..1a276f16 100644
--- a/tracker-pt/module/frame.cpp
+++ b/tracker-pt/module/frame.cpp
@@ -1,48 +1,54 @@
#include "frame.hpp"
-
#include "compat/math.hpp"
-
-#include <cstring>
-#include <tuple>
-
#include <opencv2/imgproc.hpp>
-using namespace pt_module;
+namespace pt_module {
-Preview& Preview::operator=(const pt_frame& frame_)
+void Preview::set_last_frame(const pt_frame& frame_)
{
const cv::Mat& frame = frame_.as_const<const Frame>()->mat;
- ensure_size(frame_copy, frame_out.cols, frame_out.rows, CV_8UC3);
+ const bool need_resize = frame.size != frame_copy.size;
- if (frame.channels() != 3)
+ if (frame.channels() == 1)
{
- once_only(qDebug() << "tracker/pt: camera frame depth: 3 !=" << frame.channels());
- return *this;
+ if (need_resize)
+ {
+ frame_tmp.create(frame.size(), CV_8UC3);
+ cv::cvtColor(frame, frame_tmp, cv::COLOR_GRAY2BGR);
+ cv::resize(frame_tmp, frame_copy, frame_copy.size(), 0, 0, cv::INTER_NEAREST);
+ }
+ else
+ cv::cvtColor(frame, frame_copy, cv::COLOR_GRAY2BGR);
+ }
+ else if (frame.channels() == 3)
+ {
+ if (need_resize)
+ cv::resize(frame, frame_copy, frame_copy.size(), 0, 0, cv::INTER_NEAREST);
+ else
+ frame.copyTo(frame_copy);
}
-
- const bool need_resize = frame.cols != frame_out.cols || frame.rows != frame_out.rows;
- if (need_resize)
- cv::resize(frame, frame_copy, cv::Size(frame_out.cols, frame_out.rows), 0, 0, cv::INTER_NEAREST);
else
- frame.copyTo(frame_copy);
-
- return *this;
+ {
+ eval_once(qDebug() << "tracker/pt: camera frame depth" << frame.channels() << "!= 3");
+ frame_copy.create(frame_copy.size(), CV_8UC3);
+ frame_copy.setTo({0});
+ }
}
Preview::Preview(int w, int h)
{
- ensure_size(frame_out, w, h, CV_8UC4);
-
- frame_out.setTo(cv::Scalar(0, 0, 0, 0));
+ frame_out.create(h, w, CV_8UC4);
+ frame_copy.create(h, w, CV_8UC3);
+ frame_copy.setTo({0});
}
QImage Preview::get_bitmap()
{
- int stride = frame_out.step.p[0];
+ int stride = (int)frame_out.step.p[0];
- if (stride < 64 || stride < frame_out.cols * 4)
+ if (stride < frame_out.cols * 4)
{
- once_only(qDebug() << "bad stride" << stride
+ eval_once(qDebug() << "bad stride" << stride
<< "for bitmap size" << frame_copy.cols << frame_copy.rows);
return QImage();
}
@@ -55,11 +61,9 @@ QImage Preview::get_bitmap()
QImage::Format_ARGB32);
}
-void Preview::draw_head_center(double x, double y)
+void Preview::draw_head_center(f x, f y)
{
- double px_, py_;
-
- std::tie(px_, py_) = to_pixel_pos(x, y, frame_copy.cols, frame_copy.rows);
+ auto [px_, py_] = to_pixel_pos(x, y, frame_copy.cols, frame_copy.rows);
int px = iround(px_), py = iround(py_);
@@ -76,8 +80,4 @@ void Preview::draw_head_center(double x, double y)
color, 1);
}
-void Preview::ensure_size(cv::Mat& frame, int w, int h, int type)
-{
- if (frame.cols != w || frame.rows != h)
- frame = cv::Mat(h, w, type);
-}
+} // ns pt_module
diff --git a/tracker-pt/module/frame.hpp b/tracker-pt/module/frame.hpp
index 9e4f809a..0569a323 100644
--- a/tracker-pt/module/frame.hpp
+++ b/tracker-pt/module/frame.hpp
@@ -2,9 +2,14 @@
#include "pt-api.hpp"
-#include <opencv2/core.hpp>
+#include <opencv2/core/mat.hpp>
#include <QImage>
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
namespace pt_module {
struct Frame final : pt_frame
@@ -19,18 +24,19 @@ struct Preview final : pt_preview
{
Preview(int w, int h);
- Preview& operator=(const pt_frame& frame) override;
+ void set_last_frame(const pt_frame& frame) override;
QImage get_bitmap() override;
- void draw_head_center(double x, double y) override;
+ void draw_head_center(f x, f y) override;
operator cv::Mat&() { return frame_copy; }
operator cv::Mat const&() const { return frame_copy; }
private:
- static void ensure_size(cv::Mat& frame, int w, int h, int type);
-
- bool fresh = true;
- cv::Mat frame_copy, frame_color, frame_out;
+ cv::Mat frame_copy, frame_out, frame_tmp;
};
} // ns pt_module
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
diff --git a/tracker-pt/module/lang/de_DE.ts b/tracker-pt/module/lang/de_DE.ts
new file mode 100644
index 00000000..6c548aba
--- /dev/null
+++ b/tracker-pt/module/lang/de_DE.ts
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>PointTracker 1.1</source>
+ <translation>PointTracker 1.1</translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-pt/module/lang/nl_NL.ts b/tracker-pt/module/lang/nl_NL.ts
index 9e739505..4679971e 100644
--- a/tracker-pt/module/lang/nl_NL.ts
+++ b/tracker-pt/module/lang/nl_NL.ts
@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="nl_NL">
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>PointTracker 1.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-pt/module/lang/ru_RU.ts b/tracker-pt/module/lang/ru_RU.ts
index f62cf2e1..c3611ef0 100644
--- a/tracker-pt/module/lang/ru_RU.ts
+++ b/tracker-pt/module/lang/ru_RU.ts
@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>PointTracker 1.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-pt/module/lang/stub.ts b/tracker-pt/module/lang/stub.ts
index 6401616d..03d19f4e 100644
--- a/tracker-pt/module/lang/stub.ts
+++ b/tracker-pt/module/lang/stub.ts
@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>PointTracker 1.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-pt/module/lang/zh_CN.ts b/tracker-pt/module/lang/zh_CN.ts
index 6401616d..c39728a1 100644
--- a/tracker-pt/module/lang/zh_CN.ts
+++ b/tracker-pt/module/lang/zh_CN.ts
@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>PointTracker 1.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-pt/module/module.cpp b/tracker-pt/module/module.cpp
index 5c298ca5..f665face 100644
--- a/tracker-pt/module/module.cpp
+++ b/tracker-pt/module/module.cpp
@@ -1,6 +1,6 @@
#include "ftnoir_tracker_pt.h"
-#include "api/plugin-api.hpp"
+#include "module.hpp"
#include "camera.h"
#include "frame.hpp"
#include "point_extractor.h"
@@ -12,7 +12,11 @@
static const QString module_name = "tracker-pt";
-using namespace pt_module;
+#ifdef __clang__
+# pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
+namespace pt_module {
struct pt_module_traits final : pt_runtime_traits
{
@@ -54,16 +58,15 @@ struct dialog_pt : TrackerDialog_PT
dialog_pt();
};
-class metadata_pt : public Metadata
-{
- QString name() { return _("PointTracker 1.1"); }
- QIcon icon() { return QIcon(":/Resources/Logo_IR.png"); }
-};
+dialog_pt::dialog_pt() : TrackerDialog_PT(module_name) {}
+
+QString metadata_pt::name() { return tr("PointTracker 1.1"); }
+QIcon metadata_pt::icon() { return QIcon(":/Resources/Logo_IR.png"); }
+
+}
// ns pt_module
using namespace pt_module;
-dialog_pt::dialog_pt() : TrackerDialog_PT(module_name) {}
-
OPENTRACK_DECLARE_TRACKER(tracker_pt, dialog_pt, metadata_pt)
diff --git a/tracker-pt/module/module.hpp b/tracker-pt/module/module.hpp
new file mode 100644
index 00000000..0b3f12cf
--- /dev/null
+++ b/tracker-pt/module/module.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "api/plugin-api.hpp"
+#include <QIcon>
+#include <QString>
+
+#include "compat/linkage-macros.hpp"
+
+namespace pt_module
+{
+
+class OTR_GENERIC_EXPORT metadata_pt : public Metadata
+{
+ Q_OBJECT
+
+ QString name() override;
+ QIcon icon() override;
+};
+
+} // ns pt_module
diff --git a/tracker-pt/module/point_extractor.cpp b/tracker-pt/module/point_extractor.cpp
index b67c4036..3329fafc 100644
--- a/tracker-pt/module/point_extractor.cpp
+++ b/tracker-pt/module/point_extractor.cpp
@@ -9,11 +9,11 @@
#include "point_extractor.h"
#include "point_tracker.h"
#include "frame.hpp"
-
#include "cv/numeric.hpp"
#include "compat/math.hpp"
+#include "compat/math-imports.hpp"
-#include <opencv2/videoio.hpp>
+#include <opencv2/imgproc.hpp>
#undef PREVIEW
//#define PREVIEW
@@ -29,12 +29,13 @@
#include <QDebug>
-using namespace types;
-using namespace pt_module;
+using namespace numeric_types;
+
+// meanshift code written by Michael Welter
/*
http://en.wikipedia.org/wiki/Mean-shift
-In this application the idea, is to eliminate any bias of the point estimate
+In this application the idea, is to eliminate any bias of the point estimate
which is introduced by the rather arbitrary thresholded area. One must recognize
that the thresholded area can only move in one pixel increments since it is
binary. Thus, its center of mass might make "jumps" as pixels are added/removed
@@ -43,31 +44,28 @@ With mean-shift, a moving "window" or kernel is multiplied with the gray-scale
image, and the COM is calculated of the result. This is iterated where the
kernel center is set the previously computed COM. Thus, peaks in the image intensity
distribution "pull" the kernel towards themselves. Eventually it stops moving, i.e.
-then the computed COM coincides with the kernel center. We hope that the
+then the computed COM coincides with the kernel center. We hope that the
corresponding location is a good candidate for the extracted point.
-The idea similar to the window scaling suggested in Berglund et al. "Fast, bias-free
+The idea similar to the window scaling suggested in Berglund et al. "Fast, bias-free
algorithm for tracking single particles with variable size and shape." (2008).
*/
-static cv::Vec2d MeanShiftIteration(const cv::Mat &frame_gray, const vec2 &current_center, f filter_width)
+static vec2 MeanShiftIteration(const cv::Mat1b &frame_gray, const vec2 &current_center, f filter_width)
{
- // Most amazingly this function runs faster with doubles than with floats.
- const f s = 1.0 / filter_width;
+ const f s = 1 / filter_width;
f m = 0;
- vec2 com { 0, 0 };
+ vec2 com { 0, 0 };
for (int i = 0; i < frame_gray.rows; i++)
{
- auto frame_ptr = (uint8_t const* restrict_ptr)frame_gray.ptr(i);
+ uint8_t const* const __restrict frame_ptr = frame_gray.ptr(i);
for (int j = 0; j < frame_gray.cols; j++)
{
f val = frame_ptr[j];
- val = val * val; // taking the square wights brighter parts of the image stronger.
- {
- f dx = (j - current_center[0])*s;
- f dy = (i - current_center[1])*s;
- f f = std::fmax(0, 1 - dx*dx - dy*dy);
- val *= f;
- }
+ val = val * val; // taking the square weighs brighter parts of the image stronger.
+ f dx = (j - current_center[0])*s;
+ f dy = (i - current_center[1])*s;
+ f max = std::fmax(f(0), 1 - dx*dx - dy*dy);
+ val *= max;
m += val;
com[0] += j * val;
com[1] += i * val;
@@ -75,13 +73,15 @@ static cv::Vec2d MeanShiftIteration(const cv::Mat &frame_gray, const vec2 &curre
}
if (m > f(.1))
{
- com *= f(1) / m;
+ com *= 1 / m;
return com;
}
else
return current_center;
}
+namespace pt_module {
+
PointExtractor::PointExtractor(const QString& module_name) : s(module_name)
{
blobs.reserve(max_blobs);
@@ -89,24 +89,19 @@ PointExtractor::PointExtractor(const QString& module_name) : s(module_name)
void PointExtractor::ensure_channel_buffers(const cv::Mat& orig_frame)
{
- if (ch[0].rows != orig_frame.rows || ch[0].cols != orig_frame.cols)
- for (unsigned k = 0; k < 3; k++)
- ch[k] = cv::Mat1b(orig_frame.rows, orig_frame.cols);
+ for (cv::Mat1b& x : ch)
+ x.create(orig_frame.rows, orig_frame.cols);
}
void PointExtractor::ensure_buffers(const cv::Mat& frame)
{
const int W = frame.cols, H = frame.rows;
- if (frame_gray.rows != W || frame_gray.cols != H)
- {
- frame_gray = cv::Mat1b(H, W);
- frame_bin = cv::Mat1b(H, W);
- frame_blobs = cv::Mat1b(H, W);
- }
+ frame_gray.create(H, W);
+ frame_bin.create(H, W);
}
-void PointExtractor::extract_single_channel(const cv::Mat& orig_frame, int idx, cv::Mat& dest)
+void PointExtractor::extract_single_channel(const cv::Mat& orig_frame, int idx, cv::Mat1b& dest)
{
ensure_channel_buffers(orig_frame);
@@ -117,17 +112,50 @@ void PointExtractor::extract_single_channel(const cv::Mat& orig_frame, int idx,
cv::mixChannels(&orig_frame, 1, &dest, 1, from_to, 1);
}
-void PointExtractor::extract_channels(const cv::Mat& orig_frame, const int* order, int order_npairs)
+void PointExtractor::filter_single_channel(const cv::Mat& orig_frame, float r, float g, float b, bool overexp, cv::Mat1b& dest)
{
ensure_channel_buffers(orig_frame);
- cv::mixChannels(&orig_frame, 1, (cv::Mat*) ch, order_npairs, order, order_npairs);
+ // just filter for colour or also include overexposed regions?
+ if (!overexp)
+ cv::transform(orig_frame, dest, cv::Mat(cv::Matx13f(b, g, r)));
+ else
+ {
+ for (int i = 0; i < orig_frame.rows; i++)
+ {
+ cv::Vec3b const* const __restrict orig_ptr = orig_frame.ptr<cv::Vec3b>(i);
+ uint8_t* const __restrict dest_ptr = dest.ptr(i);
+ for (int j = 0; j < orig_frame.cols; j++)
+ {
+ // get the intensity of the key color (i.e. +ve coefficients)
+ uchar blue = orig_ptr[j][0], green = orig_ptr[j][1], red = orig_ptr[j][2];
+ float key = std::max(b, 0.0f) * blue + std::max(g, 0.0f) * green + std::max(r, 0.0f) * red;
+ // get the intensity of the non-key color (i.e. -ve coefficients)
+ float nonkey = std::max(-b, 0.0f) * blue + std::max(-g, 0.0f) * green + std::max(-r, 0.0f) * red;
+ // the result is key color minus non-key color inversely weighted by key colour intensity
+ dest_ptr[j] = std::max(0.0f, std::min(255.0f, key - (255.0f - key) / 255.0f * nonkey));
+ }
+ }
+ }
}
void PointExtractor::color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output)
{
+ if (frame.channels() == 1)
+ {
+ output.create(frame.rows, frame.cols);
+ frame.copyTo(output);
+ return;
+ }
+
+ const float half_chr_key_str = *s.chroma_key_strength * 0.5;
switch (s.blob_color)
{
+ case pt_color_green_only:
+ {
+ extract_single_channel(frame, 1, output);
+ break;
+ }
case pt_color_blue_only:
{
extract_single_channel(frame, 0, output);
@@ -138,18 +166,44 @@ void PointExtractor::color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output)
extract_single_channel(frame, 2, output);
break;
}
- case pt_color_average:
+ case pt_color_red_chromakey:
+ {
+ filter_single_channel(frame, 1, -half_chr_key_str, -half_chr_key_str, s.chroma_key_overexposed, output);
+ break;
+ }
+ case pt_color_green_chromakey:
{
- const int W = frame.cols, H = frame.rows;
- const cv::Mat tmp = frame.reshape(1, W * H);
- cv::Mat output_ = output.reshape(1, W * H);
- cv::reduce(tmp, output_, 1, cv::REDUCE_AVG);
+ filter_single_channel(frame, -half_chr_key_str, 1, -half_chr_key_str, s.chroma_key_overexposed, output);
break;
}
+ case pt_color_blue_chromakey:
+ {
+ filter_single_channel(frame, -half_chr_key_str, -half_chr_key_str, 1, s.chroma_key_overexposed, output);
+ break;
+ }
+ case pt_color_cyan_chromakey:
+ {
+ filter_single_channel(frame, -*s.chroma_key_strength, 0.5, 0.5, s.chroma_key_overexposed, output);
+ break;
+ }
+ case pt_color_yellow_chromakey:
+ {
+ filter_single_channel(frame, 0.5, 0.5, -*s.chroma_key_strength, s.chroma_key_overexposed, output);
+ break;
+ }
+ case pt_color_magenta_chromakey:
+ {
+ filter_single_channel(frame, 0.5, -*s.chroma_key_strength, 0.5, s.chroma_key_overexposed, output);
+ break;
+ }
+ case pt_color_hardware:
+ eval_once(qDebug() << "camera driver doesn't support grayscale");
+ goto do_grayscale;
default:
- once_only(qDebug() << "wrong pt_color_type enum value" << int(s.blob_color));
- /*FALLTHROUGH*/
- case pt_color_natural:
+ eval_once(qDebug() << "wrong pt_color_type enum value" << int(s.blob_color));
+ [[fallthrough]];
+ case pt_color_bt709:
+do_grayscale:
cv::cvtColor(frame, output, cv::COLOR_BGR2GRAY);
break;
}
@@ -175,31 +229,97 @@ void PointExtractor::threshold_image(const cv::Mat& frame_gray, cv::Mat1b& outpu
cv::noArray(),
hist,
1,
- (int const*) &hist_size,
+ &hist_size,
&ranges);
- const f radius = (f) threshold_radius_value(frame_gray.cols, frame_gray.rows, threshold_slider_value);
+ const f radius = threshold_radius_value(frame_gray.cols, frame_gray.rows, threshold_slider_value);
- auto ptr = (float const* const restrict_ptr) hist.ptr(0);
- const unsigned area = uround(3 * M_PI * radius*radius);
+ float const* const __restrict ptr = hist.ptr<float>(0);
+ const unsigned area = unsigned(iround(3 * pi * radius*radius));
const unsigned sz = unsigned(hist.cols * hist.rows);
- unsigned thres = 32;
- for (unsigned i = sz-1, cnt = 0; i > 32; i--)
+ unsigned thres = 1;
+ for (unsigned i = sz-1, cnt = 0; i > 1; i--)
{
- cnt += ptr[i];
+ cnt += (unsigned)ptr[i];
if (cnt >= area)
break;
thres = i;
}
- cv::threshold(frame_gray, output, thres, 255, CV_THRESH_BINARY);
+ cv::threshold(frame_gray, output, thres, 255, cv::THRESH_BINARY);
}
}
-void PointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_frame_, std::vector<vec2>& points)
+static void draw_blobs(cv::Mat& preview_frame, const blob* blobs, unsigned nblobs, const cv::Size& size)
+{
+ for (unsigned k = 0; k < nblobs; k++)
+ {
+ const blob& b = blobs[k];
+
+ if (b.radius < 0)
+ continue;
+
+ const f dpi = preview_frame.cols / f(320);
+ const f offx = 10 * dpi, offy = f(7.5) * dpi;
+
+ const f cx = preview_frame.cols / f(size.width),
+ cy = preview_frame.rows / f(size.height),
+ c = std::fmax(f(1), cx+cy)/2;
+
+ cv::Point p(iround(b.pos[0] * cx), iround(b.pos[1] * cy));
+
+ auto outline_color = k >= PointModel::N_POINTS
+ ? cv::Scalar(192, 192, 192)
+ : cv::Scalar(255, 255, 0);
+
+ cv::ellipse(preview_frame, p,
+ {iround(b.rect.width/(f)2+2*c), iround(b.rect.height/(f)2+2*c)},
+ 0, 0, 360, outline_color, iround(dpi), cv::LINE_AA);
+
+ char buf[16];
+ std::snprintf(buf, sizeof(buf), "%.2fpx", (double)b.radius);
+
+ auto text_color = k >= PointModel::N_POINTS
+ ? cv::Scalar(160, 160, 160)
+ : cv::Scalar(0, 0, 255);
+
+ cv::Point pos(iround(b.pos[0]*cx+offx), iround(b.pos[1]*cy+offy));
+ cv::putText(preview_frame, buf, pos,
+ cv::FONT_HERSHEY_PLAIN, iround(dpi), text_color,
+ 1);
+ }
+}
+
+static vec2 meanshift_initial_guess(const cv::Rect rect, cv::Mat& frame_roi)
+{
+ vec2 ret = {rect.width/(f)2, rect.height/(f)2};
+
+ // compute center initial guess
+ double ynorm = 0, xnorm = 0, y = 0, x = 0;
+ for (int j = 0; j < rect.height; j++)
+ {
+ const unsigned char* __restrict ptr = frame_roi.ptr<unsigned char>(j);
+ for (int i = 0; i < rect.width; i++)
+ {
+ double val = ptr[i] * 1./255;
+ x += i * val;
+ y += j * val;
+ xnorm += val;
+ ynorm += val;
+ }
+ }
+ constexpr double eps = 1e-4;
+ if (xnorm > eps && ynorm > eps)
+ ret = { (f)(x / xnorm), (f)(y / ynorm) };
+ return ret;
+}
+
+void PointExtractor::extract_points(const pt_frame& frame_,
+ pt_preview& preview_frame_,
+ bool preview_visible,
+ std::vector<vec2>& points)
{
const cv::Mat& frame = frame_.as_const<Frame>()->mat;
- cv::Mat& preview_frame = *preview_frame_.as<Preview>();
ensure_buffers(frame);
color_to_grayscale(frame, frame_gray);
@@ -211,24 +331,24 @@ void PointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_
threshold_image(frame_gray, frame_bin);
- blobs.clear();
- frame_bin.copyTo(frame_blobs);
-
- const f region_size_min = s.min_point_size;
- const f region_size_max = s.max_point_size;
+ const f region_size_min = (f)s.min_point_size;
+ const f region_size_max = (f)s.max_point_size;
unsigned idx = 0;
- for (int y=0; y < frame_blobs.rows; y++)
+
+ blobs.clear();
+
+ for (int y=0; y < frame_bin.rows; y++)
{
- const unsigned char* ptr_bin = frame_blobs.ptr(y);
- for (int x=0; x < frame_blobs.cols; x++)
+ const unsigned char* __restrict ptr_bin = frame_bin.ptr(y);
+ for (int x=0; x < frame_bin.cols; x++)
{
if (ptr_bin[x] != 255)
continue;
idx = blobs.size() + 1;
cv::Rect rect;
- cv::floodFill(frame_blobs,
+ cv::floodFill(frame_bin,
cv::Point(x,y),
cv::Scalar(idx),
&rect,
@@ -244,8 +364,8 @@ void PointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_
for (int i=rect.y; i < ymax; i++)
{
- unsigned char const* const restrict_ptr ptr_blobs = frame_blobs.ptr(i);
- unsigned char const* const restrict_ptr ptr_gray = frame_gray.ptr(i);
+ unsigned char const* const __restrict ptr_blobs = frame_bin.ptr(i);
+ unsigned char const* const __restrict ptr_gray = frame_gray.ptr(i);
for (int j=rect.x; j < xmax; j++)
{
if (ptr_blobs[j] != idx)
@@ -257,12 +377,12 @@ void PointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_
}
}
- const double radius = std::sqrt(cnt / M_PI);
+ const f radius = std::sqrt((f)cnt) / std::sqrt(pi);
if (radius > region_size_max || radius < region_size_min)
continue;
blobs.emplace_back(radius,
- vec2(rect.width/2., rect.height/2.),
+ vec2(rect.width/f(2), rect.height/f(2)),
std::pow(f(norm), f(1.1))/cnt,
rect);
@@ -272,9 +392,7 @@ void PointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_
// XXX we could go to the next scanline unless the points are really small.
// i'd expect each point being present on at least one unique scanline
// but it turns out some people are using 2px points -sh 20180110
-#if BROKEN && 0
- break;
-#endif
+ //break;
}
}
end:
@@ -288,29 +406,22 @@ end:
for (idx = 0; idx < sz; ++idx)
{
- blob &b = blobs[idx];
- cv::Rect rect = b.rect;
-
- rect.x -= rect.width / 2;
- rect.y -= rect.height / 2;
- rect.width *= 2;
- rect.height *= 2;
- rect &= cv::Rect(0, 0, W, H); // crop at frame boundaries
-
+ blob& b = blobs[idx];
+ cv::Rect rect = b.rect & cv::Rect(0, 0, W, H); // crop at frame boundaries
cv::Mat frame_roi = frame_gray(rect);
// smaller values mean more changes. 1 makes too many changes while 1.5 makes about .1
static constexpr f radius_c = f(1.75);
const f kernel_radius = b.radius * radius_c;
- vec2 pos(rect.width/2., rect.height/2.); // position relative to ROI.
+ vec2 pos = meanshift_initial_guess(rect, frame_roi); // position relative to ROI.
for (int iter = 0; iter < 10; ++iter)
{
vec2 com_new = MeanShiftIteration(frame_roi, pos, kernel_radius);
vec2 delta = com_new - pos;
pos = com_new;
- if (delta.dot(delta) < 1e-2)
+ if (delta.dot(delta) < f(1e-3))
break;
}
@@ -318,43 +429,11 @@ end:
b.pos[1] = pos[1] + rect.y;
}
- for (unsigned k = 0; k < blobs.size(); k++)
- {
- blob& b = blobs[k];
-
- const f dpi = preview_frame.cols / f(320);
- const f offx = 10 * dpi, offy = 7.5 * dpi;
-
- const f cx = preview_frame.cols / f(frame.cols),
- cy = preview_frame.rows / f(frame.rows),
- c_ = (cx+cy)/2;
-
- static constexpr unsigned fract_bits = 16;
- static constexpr double c_fract(1 << fract_bits);
-
- cv::Point p(iround(b.pos[0] * cx * c_fract), iround(b.pos[1] * cy * c_fract));
+ if (preview_visible)
+ draw_blobs(preview_frame_.as<Frame>()->mat,
+ blobs.data(), blobs.size(),
+ frame_gray.size());
- auto circle_color = k >= PointModel::N_POINTS
- ? cv::Scalar(192, 192, 192)
- : cv::Scalar(255, 255, 0);
-
- const f overlay_size = dpi > 1.5 ? 2 : 1;
-
- cv::circle(preview_frame, p, iround((b.radius + 3.3) * c_ * c_fract), circle_color, overlay_size, cv::LINE_AA, fract_bits);
-
- char buf[16];
- buf[sizeof(buf)-1] = '\0';
- std::snprintf(buf, sizeof(buf) - 1, "%.2fpx", b.radius);
-
- auto text_color = k >= PointModel::N_POINTS
- ? cv::Scalar(160, 160, 160)
- : cv::Scalar(0, 0, 255);
-
- cv::Point pos(iround(b.pos[0]*cx+offx), iround(b.pos[1]*cy+offy));
- cv::putText(preview_frame, buf, pos,
- cv::FONT_HERSHEY_PLAIN, overlay_size, text_color,
- 1);
- }
// End of mean shift code. At this point, blob positions are updated with hopefully less noisy less biased values.
points.reserve(max_blobs);
@@ -375,3 +454,5 @@ blob::blob(f radius, const vec2& pos, f brightness, const cv::Rect& rect) :
{
//qDebug() << "radius" << radius << "pos" << pos[0] << pos[1];
}
+
+} // ns pt_module
diff --git a/tracker-pt/module/point_extractor.h b/tracker-pt/module/point_extractor.h
index eac2268c..fbfdbb0b 100644
--- a/tracker-pt/module/point_extractor.h
+++ b/tracker-pt/module/point_extractor.h
@@ -9,17 +9,15 @@
#pragma once
#include "pt-api.hpp"
-
+#include <opencv2/core/mat.hpp>
+#include <opencv2/core/types.hpp>
#include <vector>
-#include <opencv2/core.hpp>
-#include <opencv2/imgproc.hpp>
-
namespace pt_module {
-using namespace types;
+using namespace numeric_types;
-struct blob
+struct blob final
{
f radius, brightness;
vec2 pos;
@@ -33,14 +31,18 @@ class PointExtractor final : public pt_point_extractor
public:
// extracts points from frame and draws some processing info into frame, if draw_output is set
// dt: time since last call in seconds
- void extract_points(const pt_frame& frame, pt_preview& preview_frame, std::vector<vec2>& points) override;
- PointExtractor(const QString& module_name);
+ void extract_points(const pt_frame& frame,
+ pt_preview& preview_frame, bool preview_visible,
+ std::vector<vec2>& points) override;
+
+ explicit PointExtractor(const QString& module_name);
+
private:
- static constexpr inline int max_blobs = 16;
+ static constexpr int max_blobs = 16;
pt_settings s;
- cv::Mat1b frame_gray, frame_bin, frame_blobs;
+ cv::Mat1b frame_bin, frame_gray;
cv::Mat1f hist;
std::vector<blob> blobs;
cv::Mat1b ch[3];
@@ -48,8 +50,8 @@ private:
void ensure_channel_buffers(const cv::Mat& orig_frame);
void ensure_buffers(const cv::Mat& frame);
- void extract_single_channel(const cv::Mat& orig_frame, int idx, cv::Mat& dest);
- void extract_channels(const cv::Mat& orig_frame, const int* order, int order_npairs);
+ void extract_single_channel(const cv::Mat& orig_frame, int idx, cv::Mat1b& dest);
+ void filter_single_channel(const cv::Mat& orig_frame, float r, float g, float b, bool overexp, cv::Mat1b& dest);
void color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output);
void threshold_image(const cv::Mat& frame_gray, cv::Mat1b& output);
diff --git a/tracker-pt/point-filter.cpp b/tracker-pt/point-filter.cpp
new file mode 100644
index 00000000..10448fe2
--- /dev/null
+++ b/tracker-pt/point-filter.cpp
@@ -0,0 +1,72 @@
+#include "point-filter.hpp"
+#include <algorithm>
+#include <cmath>
+#include <QDebug>
+
+namespace pt_point_filter_impl {
+
+void point_filter::reset()
+{
+ t = std::nullopt;
+}
+
+const PointOrder& point_filter::operator()(const PointOrder& input, f deadzone_amount)
+{
+ using std::fmod;
+ using std::sqrt;
+ using std::pow;
+ using std::clamp;
+
+ if (!s.enable_point_filter)
+ {
+ t = std::nullopt;
+ state_ = input;
+ return state_;
+ }
+
+ if (!t)
+ {
+ t.emplace();
+ state_ = input;
+ return state_;
+ }
+
+ constexpr auto E = (f)1.75;
+ const f limit = (f)*s.point_filter_limit;
+ const f C = progn(
+ constexpr int A = 1'000'000;
+ double K = *s.point_filter_coefficient;
+ f log10_pos = -2 + (int)K, rest = (f)(.999-fmod(K, 1.)*.9);
+ return A * pow((f)10, (f)-log10_pos) * rest;
+ );
+
+ f dist = 0, dz = deadzone_amount * (f)s.point_filter_deadzone / 800; // sqrt(640^2 + 480^2)
+
+ for (unsigned i = 0; i < 3; i++)
+ {
+ vec2 tmp = input[i] - state_[i];
+ f x = sqrt(tmp.dot(tmp));
+ x = std::max((f)0, x - dz);
+ dist = std::max(dist, x);
+ }
+
+ if (dist < (f)1e-6)
+ return state_;
+
+ f dt = (f)t->elapsed_seconds(); t->start();
+ f delta = pow(dist, E) * C * dt; // gain
+
+ //qDebug() << "gain" << std::min((f)1, delta);
+
+ for (unsigned i = 0; i < 3; i++)
+ {
+ f x = clamp(delta, (f)0, limit);
+ state_[i] += x*(input[i] - state_[i]);
+ }
+
+ return state_;
+}
+
+point_filter::point_filter(const pt_settings& s) : s{s} {}
+
+} // ns pt_point_filter_impl
diff --git a/tracker-pt/point-filter.hpp b/tracker-pt/point-filter.hpp
new file mode 100644
index 00000000..c3c045dd
--- /dev/null
+++ b/tracker-pt/point-filter.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "cv/numeric.hpp"
+#include "compat/timer.hpp"
+#include "pt-settings.hpp"
+#include <optional>
+#include <array>
+
+namespace pt_point_filter_impl {
+
+using namespace numeric_types;
+using PointOrder = std::array<numeric_types::vec2, 3>;
+
+class point_filter final
+{
+ PointOrder state_;
+ std::optional<Timer> t;
+ const pt_settings& s;
+
+public:
+ void reset();
+ const PointOrder& operator()(const PointOrder& input, f deadzone_amount);
+
+ explicit point_filter(const pt_settings& s);
+ ~point_filter() = default;
+
+ OTR_DISABLE_MOVE_COPY(point_filter);
+};
+
+} // ns pt_point_filter_impl
+
+using point_filter = pt_point_filter_impl::point_filter;
diff --git a/tracker-pt/point_tracker.cpp b/tracker-pt/point_tracker.cpp
index 6116bec5..39e96038 100644
--- a/tracker-pt/point_tracker.cpp
+++ b/tracker-pt/point_tracker.cpp
@@ -8,14 +8,16 @@
#include "point_tracker.h"
#include "compat/math-imports.hpp"
-using namespace types;
-
#include <vector>
#include <algorithm>
#include <cmath>
#include <QDebug>
+namespace pt_impl {
+
+using namespace numeric_types;
+
static void get_row(const mat33& m, int i, vec3& v)
{
v[0] = m(i,0);
@@ -48,13 +50,16 @@ void PointModel::set_model(const pt_settings& s)
{
switch (s.active_model_panel)
{
+ default:
+ eval_once(qDebug() << "pt: wrong model type selected");
+ [[fallthrough]];
case Clip:
- M01 = vec3(0, static_cast<f>(s.clip_ty), -static_cast<f>(s.clip_tz));
- M02 = vec3(0, -static_cast<f>(s.clip_by), -static_cast<f>(s.clip_bz));
+ M01 = vec3(0, s.clip_ty, -s.clip_tz);
+ M02 = vec3(0, -s.clip_by, -s.clip_bz);
break;
case Cap:
- M01 = vec3(-static_cast<f>(s.cap_x), -static_cast<f>(s.cap_y), -static_cast<f>(s.cap_z));
- M02 = vec3(static_cast<f>(s.cap_x), -static_cast<f>(s.cap_y), -static_cast<f>(s.cap_z));
+ M01 = vec3(-s.cap_x, -s.cap_y, -s.cap_z);
+ M02 = vec3(s.cap_x, -s.cap_y, -s.cap_z);
break;
case Custom:
M01 = vec3(s.m01_x, s.m01_y, s.m01_z);
@@ -63,69 +68,62 @@ void PointModel::set_model(const pt_settings& s)
}
}
-void PointModel::get_d_order(const vec2* points, unsigned* d_order, const vec2& d) const
+void PointModel::get_d_order(const vec2* points, unsigned* d_order, const vec2& d)
{
+ constexpr unsigned cnt = PointModel::N_POINTS;
// fit line to orthographically projected points
using t = std::pair<f,unsigned>;
- t d_vals[3];
+ t d_vals[cnt];
// get sort indices with respect to d scalar product
- for (unsigned i = 0; i < PointModel::N_POINTS; ++i)
- d_vals[i] = t(d.dot(points[i]), i);
+ for (unsigned i = 0; i < cnt; ++i)
+ d_vals[i] = {d.dot(points[i]), i};
- std::sort(d_vals,
- d_vals + 3u,
- [](const t& a, const t& b) { return a.first < b.first; });
+ std::sort(std::begin(d_vals), std::end(d_vals),
+ [](t a, t b) { return a.first < b.first; });
- for (unsigned i = 0; i < PointModel::N_POINTS; ++i)
+ for (unsigned i = 0; i < cnt; ++i)
d_order[i] = d_vals[i].second;
}
-PointTracker::PointTracker()
-{
-}
+PointTracker::PointTracker() = default;
PointTracker::PointOrder PointTracker::find_correspondences_previous(const vec2* points,
const PointModel& model,
const pt_camera_info& info)
{
- const double fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y);
- PointTracker::PointOrder p;
- p[0] = project(vec3(0,0,0), fx);
- p[1] = project(model.M01, fx);
- p[2] = project(model.M02, fx);
+ const f fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y);
+ PointTracker::PointOrder p {
+ project(vec3(0,0,0), fx),
+ project(model.M01, fx),
+ project(model.M02, fx)
+ };
- const int diagonal = int(std::sqrt(f(info.res_x*info.res_x + info.res_y*info.res_y)));
- constexpr int div = 100;
- const int max_dist = diagonal / div; // 8 pixels for 640x480
+ constexpr unsigned sz = PointModel::N_POINTS;
// set correspondences by minimum distance to projected model point
- bool point_taken[PointModel::N_POINTS];
- for (unsigned i=0; i<PointModel::N_POINTS; ++i)
- point_taken[i] = false;
+ bool point_taken[sz] {};
- for (unsigned i=0; i<PointModel::N_POINTS; ++i)
+ for (unsigned i=0; i < sz; ++i)
{
f min_sdist = 0;
unsigned min_idx = 0;
// find closest point to projected model point i
- for (unsigned j=0; j<PointModel::N_POINTS; ++j)
+ for (unsigned j=0; j < sz; ++j)
{
vec2 d = p[i]-points[j];
f sdist = d.dot(d);
- if (sdist < min_sdist || j==0)
+ if (sdist < min_sdist || j == 0)
{
min_idx = j;
min_sdist = sdist;
}
}
- if (min_sdist > max_dist)
- return find_correspondences(points, model);
// if one point is closest to more than one model point, fallback
if (point_taken[min_idx])
{
- init_phase = true;
+ reset_state();
return find_correspondences(points, model);
}
point_taken[min_idx] = true;
@@ -135,119 +133,63 @@ PointTracker::PointOrder PointTracker::find_correspondences_previous(const vec2*
return p;
}
-bool PointTracker::maybe_use_old_point_order(const PointOrder& order, const pt_camera_info& info)
-{
- constexpr f std_width = 640, std_height = 480;
-
- PointOrder scaled_order;
-
- const f cx = std_width / info.res_x;
- const f cy = std_height / info.res_y;
-
- for (unsigned k = 0; k < 3; k++)
- {
- // note, the .y component is actually scaled by width
- scaled_order[k][0] = std_width * cx * order[k][0];
- scaled_order[k][1] = std_width * cy * order[k][1];
- }
-
- f sum = 0;
-
- for (unsigned k = 0; k < 3; k++)
- {
- vec2 tmp = prev_scaled_order[k] - scaled_order[k];
- sum += std::sqrt(tmp.dot(tmp));
- }
-
- // CAVEAT don't increase too much, it visibly loses precision
- constexpr f max_dist = f(.13);
-
- const bool validp = sum < max_dist;
-
- prev_order_valid &= validp;
-
- if (!prev_order_valid)
- {
- prev_order = order;
- prev_scaled_order = scaled_order;
- }
-
-#if 0
- {
- static Timer tt;
- static int cnt1 = 0, cnt2 = 0;
- if (tt.elapsed_ms() >= 1000)
- {
- tt.start();
- if (cnt1 + cnt2)
- {
- qDebug() << "old-order" << ((cnt1 * 100) / f(cnt1 + cnt2)) << "nsamples" << (cnt1 + cnt2);
- cnt1 = 0, cnt2 = 0;
- }
- }
- if (validp)
- cnt1++;
- else
- cnt2++;
- }
-#endif
-
- prev_order_valid = validp;
-
- return validp;
-}
-
void PointTracker::track(const std::vector<vec2>& points,
const PointModel& model,
const pt_camera_info& info,
- int init_phase_timeout)
+ int init_phase_timeout,
+ point_filter& filter,
+ f deadzone_amount)
{
- const double fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y);
+ const f fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y);
PointOrder order;
- if (init_phase_timeout > 0 && t.elapsed_ms() > init_phase_timeout)
+ if (init_phase || init_phase_timeout <= 0 || t.elapsed_ms() > init_phase_timeout)
{
- t.start();
- init_phase = true;
- }
-
- if (!(init_phase_timeout > 0 && !init_phase))
+ reset_state();
order = find_correspondences(points.data(), model);
+ }
else
order = find_correspondences_previous(points.data(), model, info);
- if (maybe_use_old_point_order(order, info) ||
- POSIT(model, order, fx) != -1)
+ if (POSIT(model, filter(order, deadzone_amount), fx) != -1)
{
init_phase = false;
t.start();
}
+ else
+ reset_state();
}
PointTracker::PointOrder PointTracker::find_correspondences(const vec2* points, const PointModel& model)
{
- static const Affine a(mat33::eye(), vec3(0, 0, 1));
+ constexpr unsigned cnt = PointModel::N_POINTS;
// We do a simple freetrack-like sorting in the init phase...
- unsigned point_d_order[PointModel::N_POINTS];
- unsigned model_d_order[PointModel::N_POINTS];
- // sort points
+ unsigned point_d_order[cnt];
+ unsigned model_d_order[cnt];
+ // calculate d and d_order for simple freetrack-like point correspondence
vec2 d(model.M01[0]-model.M02[0], model.M01[1]-model.M02[1]);
+ // sort points
model.get_d_order(points, point_d_order, d);
- // calculate d and d_order for simple freetrack-like point correspondence
- vec2 pts[3] = {
- vec2(0, 0),
- vec2(model.M01[0], model.M01[1]),
- vec2(model.M02[0], model.M02[1])
+ vec2 pts[cnt] {
+ { 0, 0 },
+ { model.M01[0], model.M01[1] },
+ { model.M02[0], model.M02[1] },
};
model.get_d_order(pts, model_d_order, d);
+
// set correspondences
PointOrder p;
- for (unsigned i = 0; i < PointModel::N_POINTS; ++i)
+ for (unsigned i = 0; i < cnt; ++i)
p[model_d_order[i]] = points[point_d_order[i]];
return p;
}
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wfloat-equal"
+#endif
+
int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f focal_length)
{
// POSIT algorithm for coplanar points as presented in
@@ -256,7 +198,7 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca
// The expected rotation used for resolving the ambiguity in POSIT:
// In every iteration step the rotation closer to R_expected is taken
- static const mat33 R_expected(X_CM.R);
+ const mat33& R_expected{X_CM_expected.R};
// initial pose = last (predicted) pose
vec3 k;
@@ -265,8 +207,7 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca
f old_epsilon_1 = 0;
f old_epsilon_2 = 0;
- f epsilon_1 = 1;
- f epsilon_2 = 1;
+ f epsilon_1, epsilon_2;
vec3 I0, J0;
vec2 I0_coeff, J0_coeff;
@@ -277,8 +218,8 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca
constexpr int max_iter = 100;
- int i=1;
- for (; i<max_iter; ++i)
+ int i;
+ for (i = 1; i < max_iter; ++i)
{
epsilon_1 = k.dot(model.M01)/Z0;
epsilon_2 = k.dot(model.M02)/Z0;
@@ -303,14 +244,14 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca
// CAVEAT don't change to comparison with an epsilon -sh 20160423
if (JJ0 == II0) {
rho = sqrt(fabs(2*IJ0));
- theta = -M_PI/4;
+ theta = -pi/4;
if (IJ0<0) theta *= -1;
}
else {
rho = sqrt(sqrt( (JJ0-II0)*(JJ0-II0) + 4*IJ0*IJ0 ));
theta = atan( -2*IJ0 / (JJ0-II0) );
// avoid branch misprediction
- theta += (JJ0 - II0 < 0) * M_PI;
+ theta += (JJ0 - II0 < 0) * pi;
theta *= f(.5);
}
@@ -321,7 +262,7 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca
J_1 = J0 + rho*sin(theta)*model.u;
J_2 = J0 - rho*sin(theta)*model.u;
- f norm_const = 1/cv::norm(I_1); // all have the same norm
+ f norm_const = (f)(1/cv::norm(I_1)); // all have the same norm
// create rotation matrices
I_1 *= norm_const; J_1 *= norm_const;
@@ -340,8 +281,8 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca
// pick the rotation solution closer to the expected one
// in simple metric d(A,B) = || I - A * B^T ||
- f R_1_deviation = cv::norm(mat33::eye() - R_expected * R_1.t());
- f R_2_deviation = cv::norm(mat33::eye() - R_expected * R_2.t());
+ f R_1_deviation = (f)(cv::norm(mat33::eye() - R_expected * R_1.t()));
+ f R_2_deviation = (f)(cv::norm(mat33::eye() - R_expected * R_2.t()));
if (R_1_deviation < R_2_deviation)
R_current = &R_1;
@@ -353,7 +294,7 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca
// check for convergence condition
const f delta = fabs(epsilon_1 - old_epsilon_1) + fabs(epsilon_2 - old_epsilon_2);
- if (!(delta > constants::eps))
+ if (delta < eps)
break;
old_epsilon_1 = epsilon_1;
@@ -373,17 +314,17 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca
int ret = std::fpclassify(r(i, j));
if (ret == FP_NAN || ret == FP_INFINITE)
{
- qDebug() << "posit nan -- R";
+ qDebug() << "posit nan R";
return -1;
}
}
- for (unsigned i = 0; i < 3; i++)
+ for (unsigned i = 0; i < 3; i++) // NOLINT(modernize-loop-convert)
{
int ret = std::fpclassify(t[i]);
if (ret == FP_NAN || ret == FP_INFINITE)
{
- qDebug() << "posit nan -- T";
+ qDebug() << "posit nan T";
return -1;
}
}
@@ -394,11 +335,17 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca
X_CM.t[1] = t[1];
X_CM.t[2] = t[2];
+ X_CM_expected = X_CM;
+
//qDebug() << "iter:" << i;
return i;
}
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
vec2 PointTracker::project(const vec3& v_M, f focal_length)
{
return project(v_M, focal_length, X_CM);
@@ -412,7 +359,8 @@ vec2 PointTracker::project(const vec3& v_M, f focal_length, const Affine& X_CM)
void PointTracker::reset_state()
{
- prev_order_valid = false;
init_phase = true;
+ X_CM_expected = {};
}
+} // ns pt_impl
diff --git a/tracker-pt/point_tracker.h b/tracker-pt/point_tracker.h
index 5e741c75..7492b4eb 100644
--- a/tracker-pt/point_tracker.h
+++ b/tracker-pt/point_tracker.h
@@ -11,28 +11,25 @@
#include "cv/affine.hpp"
#include "cv/numeric.hpp"
#include "pt-api.hpp"
+#include "point-filter.hpp"
-#include <cstddef>
-#include <memory>
#include <vector>
#include <array>
-#include <opencv2/core.hpp>
-
#include <QObject>
-namespace pt_module {
+namespace pt_impl {
// ----------------------------------------------------------------------------
// Describes a 3-point model
// nomenclature as in
// [Denis Oberkampf, Daniel F. DeMenthon, Larry S. Davis: "Iterative Pose Estimation Using Coplanar Feature Points"]
-using namespace types;
+using namespace numeric_types;
struct PointModel final
{
- static constexpr inline unsigned N_POINTS = 3;
+ static constexpr unsigned N_POINTS = 3;
vec3 M01; // M01 in model frame
vec3 M02; // M02 in model frame
@@ -43,10 +40,10 @@ struct PointModel final
enum Model { Clip, Cap, Custom };
- PointModel(const pt_settings& s);
+ explicit PointModel(const pt_settings& s);
void set_model(const pt_settings& s);
- void get_d_order(const vec2* points, unsigned* d_order, const vec2& d) const;
+ static void get_d_order(const vec2* points, unsigned* d_order, const vec2& d);
};
// ----------------------------------------------------------------------------
@@ -60,8 +57,13 @@ public:
// track the pose using the set of normalized point coordinates (x pos in range -0.5:0.5)
// f : (focal length)/(sensor width)
// dt : time since last call
- void track(const std::vector<vec2>& projected_points, const PointModel& model, const pt_camera_info& info, int init_phase_timeout);
- Affine pose() { return X_CM; }
+ void track(const std::vector<vec2>& projected_points,
+ const PointModel& model,
+ const pt_camera_info& info,
+ int init_phase_timeout,
+ point_filter& filter,
+ f deadzone_amount);
+ Affine pose() const { return X_CM; }
vec2 project(const vec3& v_M, f focal_length);
vec2 project(const vec3& v_M, f focal_length, const Affine& X_CM);
void reset_state();
@@ -70,19 +72,18 @@ private:
// the points in model order
using PointOrder = std::array<vec2, 3>;
- bool maybe_use_old_point_order(const PointOrder& order, const pt_camera_info& info);
-
- PointOrder find_correspondences(const vec2* projected_points, const PointModel &model);
+ static PointOrder find_correspondences(const vec2* projected_points, const PointModel &model);
PointOrder find_correspondences_previous(const vec2* points, const PointModel &model, const pt_camera_info& info);
- int POSIT(const PointModel& point_model, const PointOrder& order, f focal_length); // The POSIT algorithm, returns the number of iterations
+ // The POSIT algorithm, returns the number of iterations
+ int POSIT(const PointModel& point_model, const PointOrder& order, f focal_length);
- Affine X_CM; // transform from model to camera
- PointOrder prev_order, prev_scaled_order;
+ Affine X_CM; // transform from model to camera
+ Affine X_CM_expected;
Timer t;
- bool init_phase = true, prev_order_valid = false;
+ bool init_phase = true;
};
} // ns pt_impl
-using pt_module::PointTracker;
-using pt_module::PointModel;
+using PointTracker = pt_impl::PointTracker;
+using PointModel = pt_impl::PointModel;
diff --git a/tracker-pt/pt-api.cpp b/tracker-pt/pt-api.cpp
index 596590dc..d71c6e13 100644
--- a/tracker-pt/pt-api.cpp
+++ b/tracker-pt/pt-api.cpp
@@ -1,77 +1,53 @@
#include "pt-api.hpp"
#include "cv/numeric.hpp"
-using namespace types;
+using namespace numeric_types;
-pt_camera_info::pt_camera_info()
-{
-}
+pt_camera_info::pt_camera_info() = default;
-double pt_camera_info::get_focal_length(f fov, int res_x, int res_y)
+f pt_camera_info::get_focal_length(f fov, int res_x, int res_y)
{
- const double diag_len = std::sqrt(double(res_x*res_x + res_y*res_y));
- const double aspect_x = res_x / diag_len;
+ const f diag_len = std::sqrt(f(res_x*res_x + res_y*res_y));
+ const f aspect_x = res_x / diag_len;
//const double aspect_y = res_y / diag_len;
- const double diag_fov = fov * M_PI/180;
- const double fov_x = 2*std::atan(std::tan(diag_fov*.5) * aspect_x);
+ const f diag_fov = fov * pi/180;
+ const f fov_x = 2*std::atan(std::tan(diag_fov*f{.5}) * aspect_x);
//const double fov_y = 2*atan(tan(diag_fov*.5) * aspect_y);
- const double fx = .5 / std::tan(fov_x * .5);
+ const f fx = f{.5} / std::tan(fov_x * f{.5});
return fx;
//fy = .5 / tan(fov_y * .5);
//static bool once = false; if (!once) { once = true; qDebug() << "f" << ret << "fov" << (fov * 180/M_PI); }
}
-pt_camera::pt_camera()
-{
-}
-
-pt_camera::~pt_camera()
-{
-}
-
-pt_runtime_traits::pt_runtime_traits()
-{
-}
-
-pt_runtime_traits::~pt_runtime_traits()
-{
-}
-
-pt_point_extractor::pt_point_extractor()
-{
-}
-
-pt_point_extractor::~pt_point_extractor()
-{
-}
+pt_camera::pt_camera() = default;
+pt_camera::~pt_camera() = default;
+pt_runtime_traits::pt_runtime_traits() = default;
+pt_runtime_traits::~pt_runtime_traits() = default;
+pt_point_extractor::pt_point_extractor() = default;
+pt_point_extractor::~pt_point_extractor() = default;
-double pt_point_extractor::threshold_radius_value(int w, int h, int threshold)
+f pt_point_extractor::threshold_radius_value(int w, int h, int threshold)
{
- double cx = w / 640., cy = h / 480.;
+ f cx = w / f{640}, cy = h / f{480};
- const double min_radius = 1.75 * cx;
- const double max_radius = 15 * cy;
+ const f min_radius = f{1.75} * cx;
+ const f max_radius = f{30} * cy;
- const double radius = std::fmax(0., (max_radius-min_radius) * threshold / f(255) + min_radius);
+ const f radius = std::fmax(f{0}, (max_radius-min_radius) * threshold / f(255) + min_radius);
return radius;
}
-std::tuple<double, double> pt_pixel_pos_mixin::to_pixel_pos(double x, double y, int w, int h)
-{
- return std::make_tuple(w*(x+.5), .5*(h - 2*y*w));
-}
-
-std::tuple<double, double> pt_pixel_pos_mixin::to_screen_pos(double px, double py, int w, int h)
+std::tuple<f, f> pt_pixel_pos_mixin::to_pixel_pos(f x, f y, int w, int h)
{
- px *= w/(w-1.), py *= h/(h-1.);
- return std::make_tuple((px - w/2.)/w, -(py - h/2.)/w);
+ return { w*(x+f{.5}), f{.5}*(h - 2*y*w) };
}
-pt_frame::pt_frame()
+std::tuple<f, f> pt_pixel_pos_mixin::to_screen_pos(f px, f py, int w, int h)
{
+ px *= w/(w-f{1}); py *= h/(h-f{1});
+ return { (px - w/f{2})/w, -(py - h/f{2})/w };
}
-pt_frame::~pt_frame()
-{
-}
+pt_frame::pt_frame() = default;
+pt_frame::~pt_frame() = default;
diff --git a/tracker-pt/pt-api.hpp b/tracker-pt/pt-api.hpp
index de097a04..15021ff3 100644
--- a/tracker-pt/pt-api.hpp
+++ b/tracker-pt/pt-api.hpp
@@ -1,42 +1,47 @@
#pragma once
-#include "export.hpp"
-
#include "pt-settings.hpp"
#include "cv/numeric.hpp"
#include "options/options.hpp"
#include <tuple>
-#include <type_traits>
+#include <vector>
#include <memory>
-#include <opencv2/core.hpp>
-
#include <QImage>
+#include <QString>
-struct OTR_PT_EXPORT pt_camera_info final
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
+struct pt_camera_info final
{
- typedef typename types::f f;
+ using f = numeric_types::f;
pt_camera_info();
- static double get_focal_length(f fov, int res_x, int res_y);
+ static f get_focal_length(f fov, int res_x, int res_y);
- double fov = 0;
- double fps = 0;
+ f fov = 0;
+ f fps = 0;
int res_x = 0;
int res_y = 0;
- int idx = -1;
+ QString name;
+ bool use_mjpeg = false;
};
-struct OTR_PT_EXPORT pt_pixel_pos_mixin
+struct pt_pixel_pos_mixin
{
- static std::tuple<double, double> to_pixel_pos(double x, double y, int w, int h);
- static std::tuple<double, double> to_screen_pos(double px, double py, int w, int h);
+ using f = numeric_types::f;
+
+ static std::tuple<f, f> to_pixel_pos(f x, f y, int w, int h);
+ static std::tuple<f, f> to_screen_pos(f px, f py, int w, int h);
};
-struct OTR_PT_EXPORT pt_frame : pt_pixel_pos_mixin
+struct pt_frame : pt_pixel_pos_mixin
{
pt_frame();
virtual ~pt_frame();
@@ -44,62 +49,78 @@ struct OTR_PT_EXPORT pt_frame : pt_pixel_pos_mixin
template<typename t>
t* as() &
{
- using u = std::decay_t<t>;
- static_assert(std::is_convertible_v<u*, pt_frame*>, "must be derived from pt_image");
-
return static_cast<t*>(this);
}
template<typename t>
t const* as_const() const&
{
- return const_cast<pt_frame*>(this)->as<const t>();
+ return static_cast<t const*>(this);
}
+
+protected:
+ pt_frame(const pt_frame&) = default;
+ pt_frame(pt_frame&&) = default;
+ pt_frame& operator=(const pt_frame&) = default;
+ pt_frame& operator=(pt_frame&&) = default;
};
-struct OTR_PT_EXPORT pt_preview : pt_frame
+struct pt_preview : pt_frame
{
- virtual pt_preview& operator=(const pt_frame&) = 0;
+ pt_preview() = default;
+
+ OTR_DISABLE_MOVE_COPY(pt_preview);
+
+ virtual void set_last_frame(const pt_frame&) = 0;
virtual QImage get_bitmap() = 0;
- virtual void draw_head_center(double x, double y) = 0;
+ virtual void draw_head_center(f x, f y) = 0;
};
-struct OTR_PT_EXPORT pt_camera
+struct pt_camera
{
using result = std::tuple<bool, pt_camera_info>;
+ using f = numeric_types::f;
pt_camera();
virtual ~pt_camera();
- virtual warn_result_unused bool start(int idx, int fps, int res_x, int res_y) = 0;
+ OTR_DISABLE_MOVE_COPY(pt_camera);
+
+ [[nodiscard]] virtual bool start(const pt_settings& s) = 0;
virtual void stop() = 0;
- virtual warn_result_unused result get_frame(pt_frame& frame) = 0;
- virtual warn_result_unused result get_info() const = 0;
+ virtual result get_frame(pt_frame& frame) = 0;
+ virtual result get_info() const = 0;
virtual pt_camera_info get_desired() const = 0;
virtual QString get_desired_name() const = 0;
virtual QString get_active_name() const = 0;
- virtual void set_fov(double value) = 0;
+ virtual void set_fov(f value) = 0;
virtual void show_camera_settings() = 0;
+ virtual f deadzone_amount() const { return 1; }
};
-struct OTR_PT_EXPORT pt_point_extractor : pt_pixel_pos_mixin
+struct pt_point_extractor : pt_pixel_pos_mixin
{
- using vec2 = types::vec2;
+ using vec2 = numeric_types::vec2;
+ using f = numeric_types::f;
+
+ OTR_DISABLE_MOVE_COPY(pt_point_extractor);
pt_point_extractor();
virtual ~pt_point_extractor();
- virtual void extract_points(const pt_frame& image, pt_preview& preview_frame, std::vector<vec2>& points) = 0;
+ virtual void extract_points(const pt_frame& image, pt_preview& preview_frame, bool preview_visible, std::vector<vec2>& points) = 0;
- static double threshold_radius_value(int w, int h, int threshold);
+ static f threshold_radius_value(int w, int h, int threshold);
};
-struct OTR_PT_EXPORT pt_runtime_traits
+struct pt_runtime_traits
{
template<typename t> using pointer = std::shared_ptr<t>;
+ OTR_DISABLE_MOVE_COPY(pt_runtime_traits);
+
pt_runtime_traits();
virtual ~pt_runtime_traits();
@@ -109,3 +130,10 @@ struct OTR_PT_EXPORT pt_runtime_traits
virtual pointer<pt_preview> make_preview(int w, int h) const = 0;
virtual QString get_module_name() const = 0;
};
+
+template<typename t>
+using pt_pointer = typename pt_runtime_traits::pointer<t>;
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
diff --git a/tracker-pt/pt-settings.hpp b/tracker-pt/pt-settings.hpp
index 471bb941..5d16d973 100644
--- a/tracker-pt/pt-settings.hpp
+++ b/tracker-pt/pt-settings.hpp
@@ -1,6 +1,5 @@
#pragma once
-#include "export.hpp"
#include "options/options.hpp"
#include <QString>
@@ -9,19 +8,32 @@ enum pt_color_type
{
// explicit values, gotta preserve the numbering in .ini
// don't reuse when removing some of the modes
- pt_color_natural = 2,
+ pt_color_bt709 = 2,
+ pt_color_hardware = 14,
pt_color_red_only = 3,
- pt_color_average = 5,
pt_color_blue_only = 6,
+ pt_color_green_only = 7,
+ pt_color_red_chromakey = 8,
+ pt_color_green_chromakey = 9,
+ pt_color_blue_chromakey = 10,
+ pt_color_cyan_chromakey = 11,
+ pt_color_yellow_chromakey = 12,
+ pt_color_magenta_chromakey = 13,
};
+namespace pt_impl {
+
+using namespace options;
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
struct pt_settings final : options::opts
{
using slider_value = options::slider_value;
- pt_settings(const QString& name) : opts(name) {}
- ~pt_settings() {}
-
value<QString> camera_name { b, "camera-name", "" };
value<int> cam_res_x { b, "camera-res-width", 640 },
cam_res_y { b, "camera-res-height", 480 },
@@ -48,10 +60,28 @@ struct pt_settings final : options::opts
value<int> fov { b, "camera-fov", 56 };
- value<bool> dynamic_pose { b, "dynamic-pose-resolution", true };
+ value<bool> dynamic_pose { b, "dynamic-pose-resolution", false };
value<int> init_phase_timeout { b, "init-phase-timeout", 250 };
value<bool> auto_threshold { b, "automatic-threshold", true };
- value<pt_color_type> blob_color { b, "blob-color", pt_color_natural };
+ value<pt_color_type> blob_color { b, "blob-color", pt_color_bt709 };
+ value<bool> use_mjpeg { b, "use-mjpeg", false };
+ value<slider_value> chroma_key_strength{ b, "chroma-key-strength", { 1.0, 0.5, 4. } };
+ value<bool> chroma_key_overexposed{ b, "chroma-key-overexposed", false };
+
+ value<slider_value> threshold_slider { b, "threshold-slider", { 128, 0, 255 } };
- value<slider_value> threshold_slider { b, "threshold-slider", slider_value(128, 0, 255) };
+ value<bool> enable_point_filter{ b, "enable-point-filter", false };
+ value<slider_value> point_filter_coefficient { b, "point-filter-coefficient", { 1.0, 0, 4 } };
+ value<slider_value> point_filter_limit { b, "point-filter-limit", { 0.1, 0.01, 1 }};
+ value<slider_value> point_filter_deadzone { b, "point-filter-deadzone", {0, 0, 1} };
+
+ explicit pt_settings(const QString& name) : opts(name) {}
};
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
+} // ns pt_impl
+
+using pt_settings = pt_impl::pt_settings;
diff --git a/tracker-rift-025/CMakeLists.txt b/tracker-rift-025/CMakeLists.txt
deleted file mode 100644
index 36d3f08f..00000000
--- a/tracker-rift-025/CMakeLists.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-include(opentrack-rift)
-otr_rift(tracker-rift-025 SDK_RIFT_025)
-SET(SDK_RIFT_025 "" CACHE PATH "libOVR 0.2.5 path for Oculus Rift")
-
diff --git a/tracker-rift-025/ftnoir_rift_025.qrc b/tracker-rift-025/ftnoir_rift_025.qrc
deleted file mode 100644
index cd174fc4..00000000
--- a/tracker-rift-025/ftnoir_rift_025.qrc
+++ /dev/null
@@ -1,7 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file>images/rift_medium.png</file>
- <file>images/rift_small.png</file>
- <file>images/rift_tiny.png</file>
- </qresource>
-</RCC>
diff --git a/tracker-rift-025/ftnoir_rift_clientcontrols_025.ui b/tracker-rift-025/ftnoir_rift_clientcontrols_025.ui
deleted file mode 100644
index 71918a71..00000000
--- a/tracker-rift-025/ftnoir_rift_clientcontrols_025.ui
+++ /dev/null
@@ -1,176 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>dialog_rift_025</class>
- <widget class="QWidget" name="dialog_rift_025">
- <property name="windowModality">
- <enum>Qt::NonModal</enum>
- </property>
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>218</width>
- <height>200</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="windowTitle">
- <string>Oculus Rift tracker settings FaceTrackNoIR</string>
- </property>
- <property name="windowIcon">
- <iconset>
- <normaloff>images/opentrack.png</normaloff>images/opentrack.png</iconset>
- </property>
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="autoFillBackground">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="0">
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>Yaw spring</string>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
- <widget class="QCheckBox" name="yawSpring">
- <property name="text">
- <string>Enable</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Persistence</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QDoubleSpinBox" name="persistence">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="decimals">
- <number>5</number>
- </property>
- <property name="minimum">
- <double>0.900000000000000</double>
- </property>
- <property name="maximum">
- <double>1.000000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.001000000000000</double>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Constant drift</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QDoubleSpinBox" name="constantDrift">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="decimals">
- <number>5</number>
- </property>
- <property name="minimum">
- <double>0.000100000000000</double>
- </property>
- <property name="maximum">
- <double>0.100000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.001000000000000</double>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Deadzone</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QDoubleSpinBox" name="deadzone">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="decimals">
- <number>5</number>
- </property>
- <property name="maximum">
- <double>0.100000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.010000000000000</double>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
- <slots>
- <slot>startEngineClicked()</slot>
- <slot>stopEngineClicked()</slot>
- <slot>cameraSettingsClicked()</slot>
- </slots>
-</ui>
diff --git a/tracker-rift-025/ftnoir_tracker_rift_025.cpp b/tracker-rift-025/ftnoir_tracker_rift_025.cpp
deleted file mode 100644
index 8d4a6e8a..00000000
--- a/tracker-rift-025/ftnoir_tracker_rift_025.cpp
+++ /dev/null
@@ -1,118 +0,0 @@
-/* Copyright (c) 2013 mm0zct
- *
- * 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.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include "ftnoir_tracker_rift_025.h"
-#include "api/plugin-api.hpp"
-#include <OVR.h>
-#include <cstdio>
-#include <cmath>
-
-
-using namespace OVR;
-
-rift_tracker_025::rift_tracker_025()
-{
- pManager = NULL;
- pSensor = NULL;
- pSFusion = NULL;
- old_yaw = 0;
-}
-
-rift_tracker_025::~rift_tracker_025()
-{
- if (pSensor)
- pSensor->Release();
- if (pSFusion)
- delete pSFusion;
- if (pManager)
- pManager->Release();
- System::Destroy();
-}
-
-module_status rift_tracker_025::start_tracker(QFrame*)
-{
- QString err;
-
- System::Init(Log::ConfigureDefaultLog(LogMask_All));
- pManager = DeviceManager::Create();
- if (pManager != NULL)
- {
- DeviceEnumerator<OVR::SensorDevice> enumerator = pManager->EnumerateDevices<OVR::SensorDevice>();
- if (enumerator.IsAvailable())
- {
- pSensor = enumerator.CreateDevice();
-
- if (pSensor)
- {
- pSFusion = new OVR::SensorFusion();
- pSFusion->Reset();
- pSFusion->AttachToSensor(pSensor);
- }
- else
- err = tr("Unable to create Rift sensor");
-
- }
- else
- err = tr("Unable to enumerate Rift tracker");
- }
- else
- err = tr("Unable to start Rift tracker");
-
- if (err.isEmpty())
- return status_ok();
- else
- return error(err);
-}
-
-
-void rift_tracker_025::data(double *data)
-{
- if (pSFusion != NULL && pSensor != NULL)
- {
- Quatf rot = pSFusion->GetOrientation();
-
- constexpr float c_mult = 8;
- constexpr float c_div = 1/c_mult;
-
- Vector3f axis;
- float angle;
-
- rot.GetAxisAngle(&axis, &angle);
- angle *= c_div;
-
- float yaw, pitch, roll;
- Quatf(axis, angle).GetEulerAngles<Axis_Y, Axis_X, Axis_Z>(&yaw, &pitch, &roll);
-
- double yaw_ = double(yaw);
-
- if (s.useYawSpring)
- {
- yaw_ = old_yaw*s.persistence + (yaw_-old_yaw);
- if (yaw_ > s.deadzone)
- yaw_ -= s.constant_drift;
- if (yaw_ < -s.deadzone)
- yaw_ += s.constant_drift;
- old_yaw = yaw_;
- }
-
- constexpr double r2d = 180 / M_PI;
-
- data[Yaw] = yaw_ * r2d;
- data[Pitch] = double(pitch) * r2d;
- data[Roll] = double(roll) * r2d;
- }
-}
-
-OPENTRACK_DECLARE_TRACKER(rift_tracker_025, dialog_rift_025, rift_025Dll)
diff --git a/tracker-rift-025/ftnoir_tracker_rift_025.h b/tracker-rift-025/ftnoir_tracker_rift_025.h
deleted file mode 100644
index f71c3cd8..00000000
--- a/tracker-rift-025/ftnoir_tracker_rift_025.h
+++ /dev/null
@@ -1,65 +0,0 @@
-#pragma once
-#include "ui_ftnoir_rift_clientcontrols_025.h"
-#include "api/plugin-api.hpp"
-#include "options/options.hpp"
-#include <OVR.h>
-#include <cmath>
-#include <memory>
-#include <QMessageBox>
-#include <QWaitCondition>
-using namespace options;
-
-struct settings : opts {
- value<bool> useYawSpring;
- value<double> constant_drift, persistence, deadzone;
- settings() :
- opts("Rift-025"),
- useYawSpring(b, "yaw-spring", false),
- constant_drift(b, "constant-drift", 0.000005),
- persistence(b, "persistence", 0.99999),
- deadzone(b, "deadzone", 0.02)
- {}
-};
-
-class rift_tracker_025 : public QObject, public ITracker
-{
- Q_OBJECT
-
-public:
- rift_tracker_025();
- virtual ~rift_tracker_025() override;
- module_status start_tracker(QFrame *) override;
- void data(double *data) override;
-private:
- double old_yaw;
- settings s;
- static bool isInitialised;
- OVR::DeviceManager* pManager;
- OVR::SensorDevice* pSensor;
- OVR::SensorFusion* pSFusion;
-};
-
-class dialog_rift_025: public ITrackerDialog
-{
- Q_OBJECT
-public:
- dialog_rift_025();
-
- void register_tracker(ITracker *) {}
- void unregister_tracker() {}
-
-private:
- Ui::dialog_rift_025 ui;
- settings s;
-private slots:
- void doOK();
- void doCancel();
-};
-
-class rift_025Dll : public Metadata
-{
-public:
- QString name() { return otr_tr("Oculus Rift runtime 0.2.5 -- HMD"); }
- QIcon icon() { return QIcon(":/images/rift_tiny.png"); }
-};
-
diff --git a/tracker-rift-025/ftnoir_tracker_rift_dialog_025.cpp b/tracker-rift-025/ftnoir_tracker_rift_dialog_025.cpp
deleted file mode 100644
index 010fc699..00000000
--- a/tracker-rift-025/ftnoir_tracker_rift_dialog_025.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-#include "ftnoir_tracker_rift_025.h"
-#include "api/plugin-api.hpp"
-
-dialog_rift_025::dialog_rift_025()
-{
- ui.setupUi( this );
-
- connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
- connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
-
- tie_setting(s.constant_drift, ui.constantDrift);
- tie_setting(s.deadzone, ui.deadzone);
- tie_setting(s.persistence, ui.persistence);
- tie_setting(s.useYawSpring, ui.yawSpring);
-}
-
-void dialog_rift_025::doOK() {
- s.b->save();
- close();
-}
-
-void dialog_rift_025::doCancel() {
- close();
-}
-
diff --git a/tracker-rift-025/images/medium.png b/tracker-rift-025/images/medium.png
deleted file mode 100644
index a5ba49e7..00000000
--- a/tracker-rift-025/images/medium.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-025/images/rift_medium.png b/tracker-rift-025/images/rift_medium.png
deleted file mode 100644
index a5ba49e7..00000000
--- a/tracker-rift-025/images/rift_medium.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-025/images/rift_small.png b/tracker-rift-025/images/rift_small.png
deleted file mode 100644
index 3f18080c..00000000
--- a/tracker-rift-025/images/rift_small.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-025/images/rift_tiny.png b/tracker-rift-025/images/rift_tiny.png
deleted file mode 100644
index 76fe0f58..00000000
--- a/tracker-rift-025/images/rift_tiny.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-025/images/small.png b/tracker-rift-025/images/small.png
deleted file mode 100644
index 3f18080c..00000000
--- a/tracker-rift-025/images/small.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-025/images/tiny.png b/tracker-rift-025/images/tiny.png
deleted file mode 100644
index 76fe0f58..00000000
--- a/tracker-rift-025/images/tiny.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-042/CMakeLists.txt b/tracker-rift-042/CMakeLists.txt
deleted file mode 100644
index 594e7c5c..00000000
--- a/tracker-rift-042/CMakeLists.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-include(opentrack-rift)
-otr_rift(tracker-rift-042 SDK_RIFT_042)
-SET(SDK_RIFT_042 "" CACHE PATH "libOVR 0.4.2 path for Oculus Rift")
diff --git a/tracker-rift-042/ftnoir_rift_042.qrc b/tracker-rift-042/ftnoir_rift_042.qrc
deleted file mode 100644
index cd174fc4..00000000
--- a/tracker-rift-042/ftnoir_rift_042.qrc
+++ /dev/null
@@ -1,7 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file>images/rift_medium.png</file>
- <file>images/rift_small.png</file>
- <file>images/rift_tiny.png</file>
- </qresource>
-</RCC>
diff --git a/tracker-rift-042/ftnoir_rift_clientcontrols_042.ui b/tracker-rift-042/ftnoir_rift_clientcontrols_042.ui
deleted file mode 100644
index 102e16b2..00000000
--- a/tracker-rift-042/ftnoir_rift_clientcontrols_042.ui
+++ /dev/null
@@ -1,176 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>dialog_rift_042</class>
- <widget class="QWidget" name="dialog_rift_042">
- <property name="windowModality">
- <enum>Qt::NonModal</enum>
- </property>
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>218</width>
- <height>200</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="windowTitle">
- <string>Oculus Rift tracker settings FaceTrackNoIR</string>
- </property>
- <property name="windowIcon">
- <iconset>
- <normaloff>images/opentrack.png</normaloff>images/opentrack.png</iconset>
- </property>
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="autoFillBackground">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="0">
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>Yaw spring</string>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
- <widget class="QCheckBox" name="yawSpring">
- <property name="text">
- <string>Enable</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Persistence</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QDoubleSpinBox" name="persistence">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="decimals">
- <number>5</number>
- </property>
- <property name="minimum">
- <double>0.900000000000000</double>
- </property>
- <property name="maximum">
- <double>1.000000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.001000000000000</double>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Constant drift</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QDoubleSpinBox" name="constantDrift">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="decimals">
- <number>5</number>
- </property>
- <property name="minimum">
- <double>0.000100000000000</double>
- </property>
- <property name="maximum">
- <double>0.100000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.001000000000000</double>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Deadzone</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QDoubleSpinBox" name="deadzone">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="decimals">
- <number>5</number>
- </property>
- <property name="maximum">
- <double>0.100000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.010000000000000</double>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
- <slots>
- <slot>startEngineClicked()</slot>
- <slot>stopEngineClicked()</slot>
- <slot>cameraSettingsClicked()</slot>
- </slots>
-</ui>
diff --git a/tracker-rift-042/ftnoir_tracker_rift_042.cpp b/tracker-rift-042/ftnoir_tracker_rift_042.cpp
deleted file mode 100644
index c8ced38f..00000000
--- a/tracker-rift-042/ftnoir_tracker_rift_042.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/* Copyright (c) 2013 mm0zct
- *
- * 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.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include "ftnoir_tracker_rift_042.h"
-#include "api/plugin-api.hpp"
-
-#include <QString>
-
-#include <OVR_CAPI.h>
-#include <Kernel/OVR_Math.h>
-#include <cstdio>
-#include <cstring>
-#include <cmath>
-
-using namespace OVR;
-
-rift_tracker_042::rift_tracker_042() : old_yaw(0), hmd(nullptr)
-{
-}
-
-rift_tracker_042::~rift_tracker_042()
-{
- ovrHmd_Destroy(hmd);
- ovr_Shutdown();
-}
-
-module_status rift_tracker_042::start_tracker(QFrame*)
-{
- ovr_Initialize();
- hmd = ovrHmd_Create(0);
- if (hmd)
- {
- ovrHmd_ConfigureTracking(hmd, ovrTrackingCap_Orientation | ovrTrackingCap_MagYawCorrection | ovrTrackingCap_Position, ovrTrackingCap_Orientation);
- return status_ok();
- }
- else
- return error(tr("Unable to start Rift tracker: %1").arg(ovrHmd_GetLastError(nullptr)));
-}
-
-
-void rift_tracker_042::data(double *data)
-{
- if (hmd)
- {
- ovrHSWDisplayState hsw;
- std::memset(&hsw, 0, sizeof(hsw));
- ovrHmd_GetHSWDisplayState(hmd, &hsw);
- if (hsw.Displayed)
- ovrHmd_DismissHSWDisplay(hmd);
- ovrTrackingState ss = ovrHmd_GetTrackingState(hmd, 0);
- if (ss.StatusFlags & ovrStatus_OrientationTracked)
- {
- constexpr float c_mult = 16;
- constexpr float c_div = 1/c_mult;
-
- Vector3f axis;
- float angle;
-
- const Posef pose(ss.HeadPose.ThePose);
- pose.Rotation.GetAxisAngle(&axis, &angle);
- angle *= c_div;
-
- float yaw, pitch, roll;
- Quatf(axis, angle).GetEulerAngles<Axis_Y, Axis_X, Axis_Z>(&yaw, &pitch, &roll);
-
- double yaw_ = double(yaw);
- if (s.useYawSpring)
- {
- yaw_ = old_yaw*s.persistence + (yaw_ - old_yaw);
- if (yaw_ > s.deadzone)
- yaw_ -= s.constant_drift;
- if (yaw_ < -s.deadzone)
- yaw_ += s.constant_drift;
- old_yaw = yaw_;
- }
- constexpr double d2r = 180 / M_PI;
- data[Yaw] = yaw_ * -d2r;
- data[Pitch] = double(pitch) * d2r;
- data[Roll] = double(roll) * d2r;
- data[TX] = double(pose.Translation.x) * -1e2;
- data[TY] = double(pose.Translation.y) * 1e2;
- data[TZ] = double(pose.Translation.z) * 1e2;
- }
- }
-}
-
-OPENTRACK_DECLARE_TRACKER(rift_tracker_042, dialog_rift_042, rift_042Dll)
diff --git a/tracker-rift-042/ftnoir_tracker_rift_042.h b/tracker-rift-042/ftnoir_tracker_rift_042.h
deleted file mode 100644
index a662b564..00000000
--- a/tracker-rift-042/ftnoir_tracker_rift_042.h
+++ /dev/null
@@ -1,62 +0,0 @@
-#pragma once
-#include "ui_ftnoir_rift_clientcontrols_042.h"
-#include "api/plugin-api.hpp"
-#include "options/options.hpp"
-#include <OVR.h>
-#include <QMessageBox>
-#include <QWaitCondition>
-#include <cmath>
-#include <memory>
-using namespace options;
-
-struct settings : opts {
- value<bool> useYawSpring;
- value<double> constant_drift, persistence, deadzone;
- settings() :
- opts("Rift-042"),
- useYawSpring(b, "yaw-spring", false),
- constant_drift(b, "constant-drift", 0.000005),
- persistence(b, "persistence", 0.99999),
- deadzone(b, "deadzone", 0.02)
- {}
-};
-
-class rift_tracker_042 : public QObject, public ITracker
-{
- Q_OBJECT
-
-public:
- rift_tracker_042();
- virtual ~rift_tracker_042() override;
- module_status start_tracker(QFrame *) override;
- void data(double *data) override;
-private:
- double old_yaw;
- ovrHmd hmd;
- settings s;
-};
-
-class dialog_rift_042: public ITrackerDialog
-{
- Q_OBJECT
-public:
- dialog_rift_042();
-
- void register_tracker(ITracker *) {}
- void unregister_tracker() {}
-
-private:
- Ui::dialog_rift_042 ui;
- settings s;
-private slots:
- void doOK();
- void doCancel();
-};
-
-class rift_042Dll : public Metadata
-{
-public:
- QString name() { return otr_tr("Oculus Rift runtime 0.4.2 -- HMD"); }
- QIcon icon() { return QIcon(":/images/rift_tiny.png"); }
-};
-
diff --git a/tracker-rift-042/ftnoir_tracker_rift_dialog_042.cpp b/tracker-rift-042/ftnoir_tracker_rift_dialog_042.cpp
deleted file mode 100644
index 032c3c63..00000000
--- a/tracker-rift-042/ftnoir_tracker_rift_dialog_042.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-#include "ftnoir_tracker_rift_042.h"
-#include "api/plugin-api.hpp"
-
-dialog_rift_042::dialog_rift_042()
-{
- ui.setupUi( this );
-
- connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
- connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
-
- tie_setting(s.constant_drift, ui.constantDrift);
- tie_setting(s.deadzone, ui.deadzone);
- tie_setting(s.persistence, ui.persistence);
- tie_setting(s.useYawSpring, ui.yawSpring);
-}
-
-void dialog_rift_042::doOK() {
- s.b->save();
- close();
-}
-
-void dialog_rift_042::doCancel() {
- close();
-}
-
diff --git a/tracker-rift-042/images/medium.png b/tracker-rift-042/images/medium.png
deleted file mode 100644
index a5ba49e7..00000000
--- a/tracker-rift-042/images/medium.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-042/images/rift_medium.png b/tracker-rift-042/images/rift_medium.png
deleted file mode 100644
index a5ba49e7..00000000
--- a/tracker-rift-042/images/rift_medium.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-042/images/rift_small.png b/tracker-rift-042/images/rift_small.png
deleted file mode 100644
index 3f18080c..00000000
--- a/tracker-rift-042/images/rift_small.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-042/images/rift_tiny.png b/tracker-rift-042/images/rift_tiny.png
deleted file mode 100644
index 76fe0f58..00000000
--- a/tracker-rift-042/images/rift_tiny.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-042/images/small.png b/tracker-rift-042/images/small.png
deleted file mode 100644
index 3f18080c..00000000
--- a/tracker-rift-042/images/small.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-042/images/tiny.png b/tracker-rift-042/images/tiny.png
deleted file mode 100644
index 76fe0f58..00000000
--- a/tracker-rift-042/images/tiny.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-042/lang/zh_CN.ts b/tracker-rift-042/lang/zh_CN.ts
deleted file mode 100644
index 91f96f55..00000000
--- a/tracker-rift-042/lang/zh_CN.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1">
-<context>
- <name>dialog_rift_042</name>
- <message>
- <source>Oculus Rift tracker settings FaceTrackNoIR</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Yaw spring</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Constant drift</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Deadzone</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-<context>
- <name>rift_tracker_042</name>
- <message>
- <source>Unable to start Rift tracker: %1</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-</TS>
diff --git a/tracker-rift-080/CMakeLists.txt b/tracker-rift-080/CMakeLists.txt
deleted file mode 100644
index 2d89e442..00000000
--- a/tracker-rift-080/CMakeLists.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-include(opentrack-rift)
-otr_rift(tracker-rift-080 SDK_RIFT_080)
-SET(SDK_RIFT_080 "" CACHE PATH "libOVR 0.8.0 path for Oculus Rift")
diff --git a/tracker-rift-080/ftnoir_rift_080.qrc b/tracker-rift-080/ftnoir_rift_080.qrc
deleted file mode 100644
index cd174fc4..00000000
--- a/tracker-rift-080/ftnoir_rift_080.qrc
+++ /dev/null
@@ -1,7 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file>images/rift_medium.png</file>
- <file>images/rift_small.png</file>
- <file>images/rift_tiny.png</file>
- </qresource>
-</RCC>
diff --git a/tracker-rift-080/ftnoir_rift_clientcontrols_080.ui b/tracker-rift-080/ftnoir_rift_clientcontrols_080.ui
deleted file mode 100644
index cfc1b59f..00000000
--- a/tracker-rift-080/ftnoir_rift_clientcontrols_080.ui
+++ /dev/null
@@ -1,176 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>dialog_rift_080</class>
- <widget class="QWidget" name="dialog_rift_080">
- <property name="windowModality">
- <enum>Qt::NonModal</enum>
- </property>
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>218</width>
- <height>200</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="windowTitle">
- <string>Oculus Rift tracker settings FaceTrackNoIR</string>
- </property>
- <property name="windowIcon">
- <iconset>
- <normaloff>images/opentrack.png</normaloff>images/opentrack.png</iconset>
- </property>
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="autoFillBackground">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="0">
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>Yaw spring</string>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
- <widget class="QCheckBox" name="yawSpring">
- <property name="text">
- <string>Enable</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Persistence</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QDoubleSpinBox" name="persistence">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="decimals">
- <number>5</number>
- </property>
- <property name="minimum">
- <double>0.900000000000000</double>
- </property>
- <property name="maximum">
- <double>1.000000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.001000000000000</double>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Constant drift</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QDoubleSpinBox" name="constantDrift">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="decimals">
- <number>5</number>
- </property>
- <property name="minimum">
- <double>0.000100000000000</double>
- </property>
- <property name="maximum">
- <double>0.100000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.001000000000000</double>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Deadzone</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QDoubleSpinBox" name="deadzone">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="decimals">
- <number>5</number>
- </property>
- <property name="maximum">
- <double>0.100000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.010000000000000</double>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
- <slots>
- <slot>startEngineClicked()</slot>
- <slot>stopEngineClicked()</slot>
- <slot>cameraSettingsClicked()</slot>
- </slots>
-</ui>
diff --git a/tracker-rift-080/ftnoir_tracker_rift_080.cpp b/tracker-rift-080/ftnoir_tracker_rift_080.cpp
deleted file mode 100644
index c9f5e4ee..00000000
--- a/tracker-rift-080/ftnoir_tracker_rift_080.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-/* Copyright (c) 2013 mm0zct
- *
- * 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.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include "ftnoir_tracker_rift_080.h"
-#include "api/plugin-api.hpp"
-
-#include <QString>
-
-#include <Extras/OVR_Math.h>
-#include <OVR_CAPI_0_8_0.h>
-
-using namespace OVR;
-
-rift_tracker_080::rift_tracker_080() : old_yaw(0), hmd(nullptr)
-{
-}
-
-rift_tracker_080::~rift_tracker_080()
-{
- if (hmd)
- ovr_Destroy(hmd);
- ovr_Shutdown();
-}
-
-module_status rift_tracker_080::start_tracker(QFrame*)
-{
- ovrResult code;
- ovrGraphicsLuid luid = {{0}};
-
- code = ovr_Initialize(nullptr);
- if (!OVR_SUCCESS(code))
- goto error;
-
- code = ovr_Create(&hmd, &luid);
- if (!OVR_SUCCESS(code))
- goto error;
-
- ovr_ConfigureTracking(hmd,
- ovrTrackingCap_Orientation | ovrTrackingCap_MagYawCorrection | ovrTrackingCap_Position,
- ovrTrackingCap_Orientation);
-
- return status_ok();
-error:
- ovrErrorInfo err;
- err.Result = code;
- err.ErrorString[0] = '\0';
- ovr_GetLastErrorInfo(&err);
-
- QString strerror(err.ErrorString);
- if (strerror.size() == 0)
- strerror = "Unknown reason";
-
- return error(strerror);
-}
-
-void rift_tracker_080::data(double *data)
-{
- if (hmd)
- {
- ovrTrackingState ss = ovr_GetTrackingState(hmd, 0, false);
- if (ss.StatusFlags & ovrStatus_OrientationTracked)
- {
- constexpr float c_mult = 16;
- constexpr float c_div = 1/c_mult;
-
- Vector3f axis;
- float angle;
-
- const Posef pose(ss.HeadPose.ThePose);
- pose.Rotation.GetAxisAngle(&axis, &angle);
- angle *= c_div;
-
- float yaw, pitch, roll;
- Quatf(axis, angle).GetYawPitchRoll(&yaw, &pitch, &roll);
-
- yaw *= c_mult;
- pitch *= c_mult;
- roll *= c_mult;
-
- double yaw_ = double(yaw);
- if (s.useYawSpring)
- {
- yaw_ = old_yaw*s.persistence + (yaw_-old_yaw);
- if(yaw_ > s.deadzone)
- yaw_ -= s.constant_drift;
- if(yaw_ < -s.deadzone)
- yaw_ += s.constant_drift;
- old_yaw = yaw_;
- }
- constexpr double d2r = 180 / M_PI;
- data[Yaw] = yaw_ * -d2r;
- data[Pitch] = double(pitch) * d2r;
- data[Roll] = double(roll) * d2r;
- data[TX] = double(pose.Translation.x) * -1e2;
- data[TY] = double(pose.Translation.y) * 1e2;
- data[TZ] = double(pose.Translation.z) * 1e2;
- }
- }
-}
-
-OPENTRACK_DECLARE_TRACKER(rift_tracker_080, dialog_rift_080, rift_080Dll)
diff --git a/tracker-rift-080/ftnoir_tracker_rift_080.h b/tracker-rift-080/ftnoir_tracker_rift_080.h
deleted file mode 100644
index e5e92e3d..00000000
--- a/tracker-rift-080/ftnoir_tracker_rift_080.h
+++ /dev/null
@@ -1,59 +0,0 @@
-#pragma once
-#include "ui_ftnoir_rift_clientcontrols_080.h"
-#include "api/plugin-api.hpp"
-#include "options/options.hpp"
-#include <OVR.h>
-#include <cmath>
-#include <QMessageBox>
-#include <QWaitCondition>
-using namespace options;
-
-struct settings : opts {
- value<bool> useYawSpring;
- value<double> constant_drift, persistence, deadzone;
- settings() :
- opts("Rift-080"),
- useYawSpring(b, "yaw-spring", false),
- constant_drift(b, "constant-drift", 0.000005),
- persistence(b, "persistence", 0.99999),
- deadzone(b, "deadzone", 0.02)
- {}
-};
-
-class rift_tracker_080 : public ITracker
-{
-public:
- rift_tracker_080();
- ~rift_tracker_080() override;
- module_status start_tracker(QFrame *) override;
- void data(double *data) override;
-private:
- double old_yaw;
- ovrSession hmd;
- settings s;
-};
-
-class dialog_rift_080: public ITrackerDialog
-{
- Q_OBJECT
-public:
- dialog_rift_080();
-
- void register_tracker(ITracker *) {}
- void unregister_tracker() {}
-
-private:
- Ui::dialog_rift_080 ui;
- settings s;
-private slots:
- void doOK();
- void doCancel();
-};
-
-class rift_080Dll : public Metadata
-{
-public:
- QString name() { return otr_tr("Oculus Rift runtime 0.8.0 -- HMD"); }
- QIcon icon() { return QIcon(":/images/rift_tiny.png"); }
-};
-
diff --git a/tracker-rift-080/ftnoir_tracker_rift_dialog_080.cpp b/tracker-rift-080/ftnoir_tracker_rift_dialog_080.cpp
deleted file mode 100644
index 27eb54e4..00000000
--- a/tracker-rift-080/ftnoir_tracker_rift_dialog_080.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-#include "ftnoir_tracker_rift_080.h"
-#include "api/plugin-api.hpp"
-
-dialog_rift_080::dialog_rift_080()
-{
- ui.setupUi( this );
-
- connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
- connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
-
- tie_setting(s.constant_drift, ui.constantDrift);
- tie_setting(s.deadzone, ui.deadzone);
- tie_setting(s.persistence, ui.persistence);
- tie_setting(s.useYawSpring, ui.yawSpring);
-}
-
-void dialog_rift_080::doOK() {
- s.b->save();
- close();
-}
-
-void dialog_rift_080::doCancel() {
- close();
-}
-
diff --git a/tracker-rift-080/images/medium.png b/tracker-rift-080/images/medium.png
deleted file mode 100644
index a5ba49e7..00000000
--- a/tracker-rift-080/images/medium.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-080/images/rift_medium.png b/tracker-rift-080/images/rift_medium.png
deleted file mode 100644
index a5ba49e7..00000000
--- a/tracker-rift-080/images/rift_medium.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-080/images/rift_small.png b/tracker-rift-080/images/rift_small.png
deleted file mode 100644
index 3f18080c..00000000
--- a/tracker-rift-080/images/rift_small.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-080/images/rift_tiny.png b/tracker-rift-080/images/rift_tiny.png
deleted file mode 100644
index 76fe0f58..00000000
--- a/tracker-rift-080/images/rift_tiny.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-080/images/small.png b/tracker-rift-080/images/small.png
deleted file mode 100644
index 3f18080c..00000000
--- a/tracker-rift-080/images/small.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-080/images/tiny.png b/tracker-rift-080/images/tiny.png
deleted file mode 100644
index 76fe0f58..00000000
--- a/tracker-rift-080/images/tiny.png
+++ /dev/null
Binary files differ
diff --git a/tracker-rift-080/lang/nl_NL.ts b/tracker-rift-080/lang/nl_NL.ts
deleted file mode 100644
index 417c38c8..00000000
--- a/tracker-rift-080/lang/nl_NL.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1" language="nl_NL">
-<context>
- <name>dialog_rift_080</name>
- <message>
- <source>Oculus Rift tracker settings FaceTrackNoIR</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Yaw spring</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Constant drift</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Deadzone</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-</TS>
diff --git a/tracker-rift-080/lang/ru_RU.ts b/tracker-rift-080/lang/ru_RU.ts
deleted file mode 100644
index 7ffe3dbb..00000000
--- a/tracker-rift-080/lang/ru_RU.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1" language="ru_RU">
-<context>
- <name>dialog_rift_080</name>
- <message>
- <source>Oculus Rift tracker settings FaceTrackNoIR</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Yaw spring</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Constant drift</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Deadzone</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-</TS>
diff --git a/tracker-rift-080/lang/stub.ts b/tracker-rift-080/lang/stub.ts
deleted file mode 100644
index d0ea9fe0..00000000
--- a/tracker-rift-080/lang/stub.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1">
-<context>
- <name>dialog_rift_080</name>
- <message>
- <source>Oculus Rift tracker settings FaceTrackNoIR</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Yaw spring</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Constant drift</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Deadzone</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-</TS>
diff --git a/tracker-rift-080/lang/zh_CN.ts b/tracker-rift-080/lang/zh_CN.ts
deleted file mode 100644
index d0ea9fe0..00000000
--- a/tracker-rift-080/lang/zh_CN.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1">
-<context>
- <name>dialog_rift_080</name>
- <message>
- <source>Oculus Rift tracker settings FaceTrackNoIR</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Yaw spring</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Constant drift</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Deadzone</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-</TS>
diff --git a/tracker-rift-140/CMakeLists.txt b/tracker-rift-140/CMakeLists.txt
index c0235142..278effe2 100644
--- a/tracker-rift-140/CMakeLists.txt
+++ b/tracker-rift-140/CMakeLists.txt
@@ -1,3 +1,14 @@
-include(opentrack-rift)
-otr_rift(tracker-rift-140 SDK_RIFT_140)
-SET(SDK_RIFT_140 "" CACHE PATH "libOVR 1.4.0 path for Oculus Rift")
+if(WIN32)
+ SET(SDK_RIFT_140 "" CACHE PATH "libOVR 1.4.0 path for Oculus Rift")
+ if(SDK_RIFT_140)
+ include_directories("${SDK_RIFT_140}/Include")
+ if(opentrack-64bit)
+ link_directories("${SDK_RIFT_140}/Lib/Windows/x64/Release/VS2017")
+ else()
+ link_directories("${SDK_RIFT_140}/Lib/Windows/Win32/Release/VS2017")
+ endif()
+ link_libraries(LibOVR winmm setupapi ws2_32 imagehlp wbemuuid)
+
+ otr_module(tracker-rift)
+ endif()
+endif()
diff --git a/tracker-rift-140/dialog.cpp b/tracker-rift-140/dialog.cpp
index bd6ac46c..53b4a1d2 100644
--- a/tracker-rift-140/dialog.cpp
+++ b/tracker-rift-140/dialog.cpp
@@ -3,15 +3,10 @@
dialog_rift_140::dialog_rift_140()
{
- ui.setupUi( this );
+ ui.setupUi(this);
connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
-
- tie_setting(s.constant_drift, ui.constantDrift);
- tie_setting(s.deadzone, ui.deadzone);
- tie_setting(s.persistence, ui.persistence);
- tie_setting(s.useYawSpring, ui.yawSpring);
}
void dialog_rift_140::doOK() {
diff --git a/tracker-rift-140/dialog.ui b/tracker-rift-140/dialog.ui
index a440db58..b2682529 100644
--- a/tracker-rift-140/dialog.ui
+++ b/tracker-rift-140/dialog.ui
@@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>218</width>
- <height>200</height>
+ <width>184</width>
+ <height>40</height>
</rect>
</property>
<property name="sizePolicy">
@@ -34,124 +34,6 @@
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>Yaw spring</string>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
- <widget class="QCheckBox" name="yawSpring">
- <property name="text">
- <string>Enable</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Persistence</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QDoubleSpinBox" name="persistence">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="decimals">
- <number>5</number>
- </property>
- <property name="minimum">
- <double>0.900000000000000</double>
- </property>
- <property name="maximum">
- <double>1.000000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.001000000000000</double>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Constant drift</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QDoubleSpinBox" name="constantDrift">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="decimals">
- <number>5</number>
- </property>
- <property name="minimum">
- <double>0.000100000000000</double>
- </property>
- <property name="maximum">
- <double>0.100000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.001000000000000</double>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Deadzone</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QDoubleSpinBox" name="deadzone">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="decimals">
- <number>5</number>
- </property>
- <property name="maximum">
- <double>0.100000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.010000000000000</double>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
diff --git a/tracker-rift-140/lang/nl_NL.ts b/tracker-rift-140/lang/nl_NL.ts
index 04ed2ba1..d472b35c 100644
--- a/tracker-rift-140/lang/nl_NL.ts
+++ b/tracker-rift-140/lang/nl_NL.ts
@@ -7,24 +7,11 @@
<source>Oculus Rift tracker settings FaceTrackNoIR</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>rift_140Dll</name>
<message>
- <source>Yaw spring</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Constant drift</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Deadzone</source>
+ <source>Oculus Rift runtime 1.4.0 -- HMD</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rift-140/lang/ru_RU.ts b/tracker-rift-140/lang/ru_RU.ts
index a49a9017..59099270 100644
--- a/tracker-rift-140/lang/ru_RU.ts
+++ b/tracker-rift-140/lang/ru_RU.ts
@@ -7,24 +7,11 @@
<source>Oculus Rift tracker settings FaceTrackNoIR</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>rift_140Dll</name>
<message>
- <source>Yaw spring</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Constant drift</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Deadzone</source>
+ <source>Oculus Rift runtime 1.4.0 -- HMD</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rift-140/lang/stub.ts b/tracker-rift-140/lang/stub.ts
index d0b352bb..26ab2040 100644
--- a/tracker-rift-140/lang/stub.ts
+++ b/tracker-rift-140/lang/stub.ts
@@ -7,24 +7,11 @@
<source>Oculus Rift tracker settings FaceTrackNoIR</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>rift_140Dll</name>
<message>
- <source>Yaw spring</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Constant drift</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Deadzone</source>
+ <source>Oculus Rift runtime 1.4.0 -- HMD</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rift-140/lang/zh_CN.ts b/tracker-rift-140/lang/zh_CN.ts
index d0b352bb..ff552d19 100644
--- a/tracker-rift-140/lang/zh_CN.ts
+++ b/tracker-rift-140/lang/zh_CN.ts
@@ -1,30 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>dialog_rift_140</name>
<message>
<source>Oculus Rift tracker settings FaceTrackNoIR</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>rift_140Dll</name>
<message>
- <source>Yaw spring</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Enable</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Persistence</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Constant drift</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Deadzone</source>
+ <source>Oculus Rift runtime 1.4.0 -- HMD</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rift-140/rift-140.cpp b/tracker-rift-140/rift-140.cpp
index bf02191a..164b47f7 100644
--- a/tracker-rift-140/rift-140.cpp
+++ b/tracker-rift-140/rift-140.cpp
@@ -21,9 +21,7 @@
using namespace OVR;
-rift_tracker_140::rift_tracker_140() : old_yaw(0), hmd(nullptr)
-{
-}
+rift_tracker_140::rift_tracker_140() = default;
rift_tracker_140::~rift_tracker_140()
{
@@ -39,7 +37,10 @@ module_status rift_tracker_140::start_tracker(QFrame*)
if (OVR_FAILURE(ovr_Initialize(nullptr)))
goto error;
- if(OVR_FAILURE(ovr_Create(&hmd, &luid)))
+ if (OVR_FAILURE(ovr_Create(&hmd, &luid)))
+ goto error;
+
+ if (OVR_FAILURE(ovr_SetTrackingOriginType(hmd, ovrTrackingOrigin_EyeLevel)))
goto error;
return status_ok();
@@ -49,13 +50,13 @@ error:
ovrErrorInfo err;
ovr_GetLastErrorInfo(&err);
- QString strerror(err.ErrorString);
- if (strerror.size() == 0)
- strerror = QStringLiteral("Unknown reason #%1").arg(err.Result);
+ QString error_string(err.ErrorString);
+ if (error_string.size() == 0)
+ error_string = QStringLiteral("Unknown reason #%1").arg(err.Result);
ovr_Shutdown();
- return error(strerror);
+ return error(error_string);
}
void rift_tracker_140::data(double *data)
@@ -82,25 +83,21 @@ void rift_tracker_140::data(double *data)
pitch *= c_mult;
roll *= c_mult;
- double yaw_ = double(yaw);
- if (s.useYawSpring)
- {
- yaw_ = old_yaw*s.persistence + (yaw_-old_yaw);
- if(yaw_ > s.deadzone)
- yaw_ -= s.constant_drift;
- if(yaw_ < -s.deadzone)
- yaw_ += s.constant_drift;
- old_yaw = yaw_;
- }
constexpr double d2r = 180 / M_PI;
- data[Yaw] = yaw_ * -d2r;
- data[Pitch] = double(pitch) * d2r;
- data[Roll] = double(roll) * d2r;
- data[TX] = double(pose.Translation.x) * -1e2;
- data[TY] = double(pose.Translation.y) * 1e2;
- data[TZ] = double(pose.Translation.z) * 1e2;
+ data[Yaw] = double(yaw) * -d2r;
+ data[Pitch] = double(pitch) * d2r;
+ data[Roll] = double(roll) * d2r;
+ data[TX] = double(pose.Translation.x) * -1e2;
+ data[TY] = double(pose.Translation.y) * 1e2;
+ data[TZ] = double(pose.Translation.z) * 1e2;
}
}
}
+bool rift_tracker_140::center()
+{
+ (void)ovr_RecenterTrackingOrigin(hmd);
+ return false;
+}
+
OPENTRACK_DECLARE_TRACKER(rift_tracker_140, dialog_rift_140, rift_140Dll)
diff --git a/tracker-rift-140/rift-140.hpp b/tracker-rift-140/rift-140.hpp
index 1a6714d5..9cc34efa 100644
--- a/tracker-rift-140/rift-140.hpp
+++ b/tracker-rift-140/rift-140.hpp
@@ -9,14 +9,7 @@
using namespace options;
struct settings : opts {
- value<bool> useYawSpring;
- value<double> constant_drift, persistence, deadzone;
- settings() :
- opts("Rift-140"),
- useYawSpring(b, "yaw-spring", false),
- constant_drift(b, "constant-drift", 0.000005),
- persistence(b, "persistence", 0.99999),
- deadzone(b, "deadzone", 0.02)
+ settings() : opts("Rift-140")
{}
};
@@ -27,10 +20,11 @@ public:
~rift_tracker_140() override;
module_status start_tracker(QFrame *) override;
void data(double *data) override;
+ bool center() override;
+
private:
- double old_yaw;
- ovrSession hmd;
- ovrGraphicsLuid luid;
+ ovrSession hmd = nullptr;
+ ovrGraphicsLuid luid {};
settings s;
};
@@ -40,8 +34,8 @@ class dialog_rift_140: public ITrackerDialog
public:
dialog_rift_140();
- void register_tracker(ITracker *) {}
- void unregister_tracker() {}
+ void register_tracker(ITracker*) override {}
+ void unregister_tracker() override {}
private:
Ui::dialog_rift_140 ui;
@@ -53,8 +47,9 @@ private slots:
class rift_140Dll : public Metadata
{
-public:
- QString name() { return otr_tr("Oculus Rift runtime 1.4.0 -- HMD"); }
- QIcon icon() { return QIcon(":/images/rift_tiny.png"); }
+ Q_OBJECT
+
+ QString name() override { return tr("Oculus Rift runtime 1.4.0 -- HMD"); }
+ QIcon icon() override { return QIcon(":/images/rift_tiny.png"); }
};
diff --git a/tracker-rs/CMakeLists.txt b/tracker-rs/CMakeLists.txt
index fc8eb75b..ca439a09 100644
--- a/tracker-rs/CMakeLists.txt
+++ b/tracker-rs/CMakeLists.txt
@@ -1,12 +1,10 @@
set(SDK_REALSENSE "$ENV{RSSDK_DIR}" CACHE PATH "Intel RealSense SDK dir")
-if(WIN32 AND SDK_REALSENSE)
+if(WIN32 AND SDK_REALSENSE AND opentrack-intel)
if(CMAKE_COMPILER_IS_GNUCC)
add_definitions(-fpermissive -Wno-error) # for SDK headers
endif()
otr_module(tracker-rs)
- install(FILES "${CMAKE_SOURCE_DIR}/tracker-rs/redist/intel_rs_sdk_runtime_websetup_10.0.26.0396.exe" DESTINATION ${opentrack-contrib-pfx} PERMISSIONS ${opentrack-perms-exec})
- install(FILES "${CMAKE_SOURCE_DIR}/tracker-rs/redist/RS_EULA.rtf" DESTINATION ${opentrack-contrib-pfx} PERMISSIONS ${opentrack-perms-file})
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/rs_impl")
target_link_libraries(opentrack-tracker-rs opentrack-tracker-rs-impl)
endif()
diff --git a/tracker-rs/README.md b/tracker-rs/README.md
index 7ea4ad86..a603878b 100644
--- a/tracker-rs/README.md
+++ b/tracker-rs/README.md
@@ -3,7 +3,7 @@ This is a tracker providing markerless 3D head tracking using the Intel® RealSe
More information on RealSense can be found on [Intel.com](http://www.intel.com/content/www/us/en/architecture-and-technology/realsense-overview.html)
-This tracker uses a separate library that gets data from the SDK. Its sources are under rs_impl and can be recompiled by calling build.bat, which depends on Microsoft Visual C++ Compiler and the [Intel® RealSense™ SDK 2016 R2](http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/9078/intel_rs_sdk_offline_package_10.0.26.0396.exe)
+This tracker uses a separate library that gets data from the SDK. Its sources are under rs_impl and can be recompiled by calling build.bat, which depends on Microsoft Visual C++ Compiler and the [Intel® RealSense™ SDK 2016 R3](http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/9078/intel_rs_sdk_offline_package_10.0.26.0396.exe)
# ISC License
Copyright (c) 2015-2017, Intel Corporation
diff --git a/tracker-rs/ftnoir_tracker_rs.cpp b/tracker-rs/ftnoir_tracker_rs.cpp
index 7918b6dc..f48f58d1 100644
--- a/tracker-rs/ftnoir_tracker_rs.cpp
+++ b/tracker-rs/ftnoir_tracker_rs.cpp
@@ -13,6 +13,9 @@
#include <QMessageBox>
#include <QProcess>
#include <QStackedLayout>
+#include <QDesktopServices>
+#include <QUrl>
+#include <QPushButton>
#include <QDebug>
RSTracker::RSTracker() {
@@ -33,9 +36,7 @@ void RSTracker::configurePreviewFrame()
mImageWidget = new ImageWidget(mPreviewFrame);
mImageWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
- if(mPreviewFrame->layout() != nullptr){
- delete mPreviewFrame->layout();
- }
+ delete mPreviewFrame->layout();
QLayout* layout = new QStackedLayout();
mPreviewFrame->setLayout(layout);
@@ -83,29 +84,16 @@ void RSTracker::handleTrackingEnded(int exitCode){
showRealSenseErrorMessageBox(exitCode);
}
-bool RSTracker::startSdkInstallationProcess()
-{
- static const QString contrib_path(OPENTRACK_BASE_PATH + OPENTRACK_CONTRIB_PATH);
-
- bool pStarted = QProcess::startDetached(contrib_path + "intel_rs_sdk_runtime_websetup_10.0.26.0396.exe", QStringList({ "--finstall=core,face3d","--fnone=all" }));
- if(!pStarted){
- QMessageBox::warning(nullptr,
- tr("Intel® RealSense™ Runtime Installation"),
- tr("Installation process failed to start."),
- QMessageBox::Ok);
- }
- return pStarted;
-}
-
void RSTracker::showRealSenseErrorMessageBox(int exitCode)
{
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Critical);
msgBox.setText("RealSense Tracking Error");
- switch(exitCode){
+ switch(exitCode)
+ {
case -101: //The implementation got an invalid handle from the RealSense SDK session/modules
- msgBox.setInformativeText(tr("Couldn't initialize RealSense tracking. Please make sure SDK Runtime 2016 R2 is installed."));
+ msgBox.setInformativeText(tr("Couldn't initialize RealSense tracking. Open the tracker settings dialog for links to the camera driver and the SDK."));
break;
case -301: //RealSense SDK runtime execution aborted.
msgBox.setInformativeText(tr("Tracking stopped after the RealSense SDK Runtime execution has aborted."));
@@ -117,12 +105,8 @@ void RSTracker::showRealSenseErrorMessageBox(int exitCode)
msgBox.setInformativeText("Status code: " + QString::number(exitCode) + ".\n\nNote that you need the latest camera drivers and the SDK runtime 2016 R2 to be installed.");
}
- QPushButton* triggerSdkInstallation = msgBox.addButton(tr("Install Runtime"), QMessageBox::ActionRole);
msgBox.addButton(QMessageBox::Ok);
msgBox.exec();
-
- if(msgBox.clickedButton() == triggerSdkInstallation)
- startSdkInstallationProcess();
}
void RSTracker::data(double *data)
@@ -135,10 +119,8 @@ RSTracker::~RSTracker() {
stopPreview();
- if(mImageWidget!=nullptr)
- delete mImageWidget;
-
- if (mPreviewFrame!=nullptr && mPreviewFrame->layout()!=nullptr)
+ delete mImageWidget;
+ if (mPreviewFrame)
delete mPreviewFrame->layout();
mTrackerWorkerThread.requestInterruption();
@@ -147,7 +129,7 @@ RSTracker::~RSTracker() {
}
QString RSTrackerMetaData::name() {
- return otr_tr("Intel® RealSense™ Technology");
+ return tr("Intel® RealSense™ Technology");
}
QIcon RSTrackerMetaData::icon() {
diff --git a/tracker-rs/ftnoir_tracker_rs.h b/tracker-rs/ftnoir_tracker_rs.h
index bcf787a0..8c540ff8 100644
--- a/tracker-rs/ftnoir_tracker_rs.h
+++ b/tracker-rs/ftnoir_tracker_rs.h
@@ -20,13 +20,10 @@ class RSTracker : public QObject, public ITracker
public:
RSTracker();
- ~RSTracker();
+ ~RSTracker() override;
module_status start_tracker(QFrame *) override;
void data(double *data) override;
-public slots:
- static bool startSdkInstallationProcess();
-
protected:
void configurePreviewFrame();
@@ -48,7 +45,8 @@ private slots:
class RSTrackerMetaData : public Metadata
{
-public:
- QString name();
- QIcon icon();
+ Q_OBJECT
+
+ QString name() override;
+ QIcon icon() override;
};
diff --git a/tracker-rs/ftnoir_tracker_rs_controls.cpp b/tracker-rs/ftnoir_tracker_rs_controls.cpp
index 3ea2a0a1..230fd9a4 100644
--- a/tracker-rs/ftnoir_tracker_rs_controls.cpp
+++ b/tracker-rs/ftnoir_tracker_rs_controls.cpp
@@ -10,16 +10,13 @@
RSdialog_realsense::RSdialog_realsense()
{
ui.setupUi(this);
- connect(ui.triggerSDKInstallButton, SIGNAL(clicked(bool)), this, SLOT(doInstallRSRuntime()));
- connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
- connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
+ connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &QDialog::close);
+ connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close);
}
-void RSdialog_realsense::doInstallRSRuntime()
+void RSdialog_realsense::set_buttons_visible(bool x)
{
- bool pStarted = RSTracker::startSdkInstallationProcess();
- if(pStarted == true)
- close();
+ ui.buttonBox->setVisible(x);
}
void RSdialog_realsense::doOK()
diff --git a/tracker-rs/ftnoir_tracker_rs_controls.h b/tracker-rs/ftnoir_tracker_rs_controls.h
index eeb6ac29..b613518d 100644
--- a/tracker-rs/ftnoir_tracker_rs_controls.h
+++ b/tracker-rs/ftnoir_tracker_rs_controls.h
@@ -16,10 +16,11 @@ public:
RSdialog_realsense();
void register_tracker(ITracker *) {}
void unregister_tracker() {}
+ bool embeddable() noexcept override { return true; }
+ void set_buttons_visible(bool x) override;
private:
Ui::UIRSControls ui;
private slots:
void doOK();
void doCancel();
- void doInstallRSRuntime();
};
diff --git a/tracker-rs/ftnoir_tracker_rs_controls.ui b/tracker-rs/ftnoir_tracker_rs_controls.ui
index 4200302d..7dce262f 100644
--- a/tracker-rs/ftnoir_tracker_rs_controls.ui
+++ b/tracker-rs/ftnoir_tracker_rs_controls.ui
@@ -2,19 +2,28 @@
<ui version="4.0">
<class>UIRSControls</class>
<widget class="QWidget" name="UIRSControls">
- <property name="windowModality">
- <enum>Qt::NonModal</enum>
- </property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>484</width>
- <height>244</height>
+ <width>596</width>
+ <height>182</height>
</rect>
</property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>339</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="windowTitle">
- <string>RealSense 3D Tracker settings</string>
+ <string>Intel RealSense</string>
</property>
<property name="windowIcon">
<iconset>
@@ -23,33 +32,52 @@
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
- <property name="autoFillBackground">
- <bool>false</bool>
- </property>
<layout class="QVBoxLayout" name="verticalLayout">
- <item>
+ <item alignment="Qt::AlignTop">
<widget class="QLabel" name="label">
+ <property name="minimumSize">
+ <size>
+ <width>578</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="text">
- <string>There is no configuration available at the moment.
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;
+In order to use this tracker, you need:
-The application will activate your camera for face tracking by the
-Intel® RealSense™ SDK. By design, the application has no direct access
-to any camera images.
-
-In order to use this tracker, you need a PC equipped with an Intel® RealSense™
-F200 or SR300 camera and the RealSenseâ„¢ SDK 2016 R2 runtime.</string>
+&lt;ul&gt;
+&lt;li&gt;
+ A PC equipped with an Intel® RealSense™ F200 or SR300 camera
+&lt;/li&gt;
+&lt;li&gt;
+ Depth Camera Manager &lt;a href=&quot;https://www.intel.com/content/www/us/en/download/18309/intel-realsense-depth-camera-manager.html&quot;&gt;&lt;span&gt;driver&lt;/span&gt;&lt;/a&gt;
+ &lt;blockquote&gt;
+ To avoid problems with the DCM installer updating the camera's firmware it is recomended to run the DCM installer from the command line with the following flags:&lt;br/&gt;
+ &lt;span style=&quot; font-weight: bold; font-family:monospace, 'Courier New';&quot;&gt;--ignore-fw-update --silent --no-progress --acceptlicense=yes&lt;/span&gt;
+ &lt;/blockquote&gt;
+&lt;/li&gt;
+&lt;li&gt;
+ RealSenseâ„¢ &lt;a href=&quot;http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/9078/intel_rs_sdk_offline_package_10.0.26.0396.exe&quot;&gt;&lt;span&gt;SDK 2016 R2 runtime&lt;/span&gt;&lt;/a&gt;
+&lt;/li&gt;
+&lt;/ul&gt;
+&lt;/body&gt;&lt;/html&gt;</string>
</property>
- </widget>
- </item>
- <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
- <widget class="QPushButton" name="triggerSDKInstallButton">
- <property name="text">
- <string>Install Runtime</string>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
diff --git a/tracker-rs/ftnoir_tracker_rs_worker.cpp b/tracker-rs/ftnoir_tracker_rs_worker.cpp
index 0e2c86f4..15843824 100644
--- a/tracker-rs/ftnoir_tracker_rs_worker.cpp
+++ b/tracker-rs/ftnoir_tracker_rs_worker.cpp
@@ -7,6 +7,7 @@
#include "ftnoir_tracker_rs_worker.h"
#include "rs_impl/ftnoir_tracker_rs_impl.h"
+#include <cstdlib>
#include <QImage>
#include <QDebug>
diff --git a/tracker-rs/ftnoir_tracker_rs_worker.h b/tracker-rs/ftnoir_tracker_rs_worker.h
index 098d6c10..8758e520 100644
--- a/tracker-rs/ftnoir_tracker_rs_worker.h
+++ b/tracker-rs/ftnoir_tracker_rs_worker.h
@@ -16,7 +16,7 @@ class RSTrackerWorkerThread : public QThread
public:
RSTrackerWorkerThread();
- ~RSTrackerWorkerThread();
+ ~RSTrackerWorkerThread() override;
void getPose(double* pose);
const QImage getPreview();
diff --git a/tracker-rs/imagewidget.cpp b/tracker-rs/imagewidget.cpp
index 63986234..50a904f5 100644
--- a/tracker-rs/imagewidget.cpp
+++ b/tracker-rs/imagewidget.cpp
@@ -6,6 +6,7 @@
*/
#include "imagewidget.h"
+#include <utility>
#include <QPainter>
ImageWidget::ImageWidget(QWidget *parent) :
@@ -14,11 +15,11 @@ ImageWidget::ImageWidget(QWidget *parent) :
mImage.fill(Qt::gray);
}
-void ImageWidget::setImage(const QImage image)
+void ImageWidget::setImage(QImage image)
{
{
QMutexLocker lock(&mMutex);
- mImage = image;
+ mImage = std::move(image);
}
repaint();
}
diff --git a/tracker-rs/imagewidget.h b/tracker-rs/imagewidget.h
index e8b6e61e..a6d5932b 100644
--- a/tracker-rs/imagewidget.h
+++ b/tracker-rs/imagewidget.h
@@ -14,8 +14,8 @@
class ImageWidget : public QWidget {
Q_OBJECT
public:
- ImageWidget(QWidget* parent = 0);
- void setImage(const QImage image);
+ ImageWidget(QWidget* parent = nullptr);
+ void setImage(QImage image);
private:
QImage mImage;
diff --git a/tracker-rs/lang/nl_NL.ts b/tracker-rs/lang/nl_NL.ts
index 7fc37d0c..f129ea58 100644
--- a/tracker-rs/lang/nl_NL.ts
+++ b/tracker-rs/lang/nl_NL.ts
@@ -4,15 +4,7 @@
<context>
<name>RSTracker</name>
<message>
- <source>Intel® RealSense™ Runtime Installation</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Installation process failed to start.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Couldn&apos;t initialize RealSense tracking. Please make sure SDK Runtime 2016 R2 is installed.</source>
+ <source>Couldn&apos;t initialize RealSense tracking. Open the tracker settings dialog for links to the camera driver and the SDK.</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -23,30 +15,40 @@
<source>Tracking stopped after another program changed camera streams configuration.</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>RSTrackerMetaData</name>
<message>
- <source>Install Runtime</source>
+ <source>Intel® RealSense™ Technology</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>UIRSControls</name>
<message>
- <source>RealSense 3D Tracker settings</source>
+ <source>Intel RealSense</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>There is no configuration available at the moment.
-
-The application will activate your camera for face tracking by the
-Intel® RealSense™ SDK. By design, the application has no direct access
-to any camera images.
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;
+In order to use this tracker, you need:
-In order to use this tracker, you need a PC equipped with an Intel® RealSense™
-F200 or SR300 camera and the RealSenseâ„¢ SDK 2016 R2 runtime.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Install Runtime</source>
+&lt;ul&gt;
+&lt;li&gt;
+ A PC equipped with an Intel® RealSense™ F200 or SR300 camera
+&lt;/li&gt;
+&lt;li&gt;
+ Depth Camera Manager &lt;a href=&quot;https://www.intel.com/content/www/us/en/download/18309/intel-realsense-depth-camera-manager.html&quot;&gt;&lt;span&gt;driver&lt;/span&gt;&lt;/a&gt;
+ &lt;blockquote&gt;
+ To avoid problems with the DCM installer updating the camera&apos;s firmware it is recomended to run the DCM installer from the command line with the following flags:&lt;br/&gt;
+ &lt;span style=&quot; font-weight: bold; font-family:monospace, &apos;Courier New&apos;;&quot;&gt;--ignore-fw-update --silent --no-progress --acceptlicense=yes&lt;/span&gt;
+ &lt;/blockquote&gt;
+&lt;/li&gt;
+&lt;li&gt;
+ RealSenseâ„¢ &lt;a href=&quot;http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/9078/intel_rs_sdk_offline_package_10.0.26.0396.exe&quot;&gt;&lt;span&gt;SDK 2016 R2 runtime&lt;/span&gt;&lt;/a&gt;
+&lt;/li&gt;
+&lt;/ul&gt;
+&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rs/lang/ru_RU.ts b/tracker-rs/lang/ru_RU.ts
index 8f9a419b..4836eb8f 100644
--- a/tracker-rs/lang/ru_RU.ts
+++ b/tracker-rs/lang/ru_RU.ts
@@ -4,15 +4,7 @@
<context>
<name>RSTracker</name>
<message>
- <source>Intel® RealSense™ Runtime Installation</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Installation process failed to start.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Couldn&apos;t initialize RealSense tracking. Please make sure SDK Runtime 2016 R2 is installed.</source>
+ <source>Couldn&apos;t initialize RealSense tracking. Open the tracker settings dialog for links to the camera driver and the SDK.</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -23,30 +15,40 @@
<source>Tracking stopped after another program changed camera streams configuration.</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>RSTrackerMetaData</name>
<message>
- <source>Install Runtime</source>
+ <source>Intel® RealSense™ Technology</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>UIRSControls</name>
<message>
- <source>RealSense 3D Tracker settings</source>
+ <source>Intel RealSense</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>There is no configuration available at the moment.
-
-The application will activate your camera for face tracking by the
-Intel® RealSense™ SDK. By design, the application has no direct access
-to any camera images.
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;
+In order to use this tracker, you need:
-In order to use this tracker, you need a PC equipped with an Intel® RealSense™
-F200 or SR300 camera and the RealSenseâ„¢ SDK 2016 R2 runtime.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Install Runtime</source>
+&lt;ul&gt;
+&lt;li&gt;
+ A PC equipped with an Intel® RealSense™ F200 or SR300 camera
+&lt;/li&gt;
+&lt;li&gt;
+ Depth Camera Manager &lt;a href=&quot;https://www.intel.com/content/www/us/en/download/18309/intel-realsense-depth-camera-manager.html&quot;&gt;&lt;span&gt;driver&lt;/span&gt;&lt;/a&gt;
+ &lt;blockquote&gt;
+ To avoid problems with the DCM installer updating the camera&apos;s firmware it is recomended to run the DCM installer from the command line with the following flags:&lt;br/&gt;
+ &lt;span style=&quot; font-weight: bold; font-family:monospace, &apos;Courier New&apos;;&quot;&gt;--ignore-fw-update --silent --no-progress --acceptlicense=yes&lt;/span&gt;
+ &lt;/blockquote&gt;
+&lt;/li&gt;
+&lt;li&gt;
+ RealSenseâ„¢ &lt;a href=&quot;http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/9078/intel_rs_sdk_offline_package_10.0.26.0396.exe&quot;&gt;&lt;span&gt;SDK 2016 R2 runtime&lt;/span&gt;&lt;/a&gt;
+&lt;/li&gt;
+&lt;/ul&gt;
+&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rs/lang/stub.ts b/tracker-rs/lang/stub.ts
index 4f82f60d..8ac8297f 100644
--- a/tracker-rs/lang/stub.ts
+++ b/tracker-rs/lang/stub.ts
@@ -4,15 +4,7 @@
<context>
<name>RSTracker</name>
<message>
- <source>Intel® RealSense™ Runtime Installation</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Installation process failed to start.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Couldn&apos;t initialize RealSense tracking. Please make sure SDK Runtime 2016 R2 is installed.</source>
+ <source>Couldn&apos;t initialize RealSense tracking. Open the tracker settings dialog for links to the camera driver and the SDK.</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -23,30 +15,40 @@
<source>Tracking stopped after another program changed camera streams configuration.</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>RSTrackerMetaData</name>
<message>
- <source>Install Runtime</source>
+ <source>Intel® RealSense™ Technology</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>UIRSControls</name>
<message>
- <source>RealSense 3D Tracker settings</source>
+ <source>Intel RealSense</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>There is no configuration available at the moment.
-
-The application will activate your camera for face tracking by the
-Intel® RealSense™ SDK. By design, the application has no direct access
-to any camera images.
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;
+In order to use this tracker, you need:
-In order to use this tracker, you need a PC equipped with an Intel® RealSense™
-F200 or SR300 camera and the RealSenseâ„¢ SDK 2016 R2 runtime.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Install Runtime</source>
+&lt;ul&gt;
+&lt;li&gt;
+ A PC equipped with an Intel® RealSense™ F200 or SR300 camera
+&lt;/li&gt;
+&lt;li&gt;
+ Depth Camera Manager &lt;a href=&quot;https://www.intel.com/content/www/us/en/download/18309/intel-realsense-depth-camera-manager.html&quot;&gt;&lt;span&gt;driver&lt;/span&gt;&lt;/a&gt;
+ &lt;blockquote&gt;
+ To avoid problems with the DCM installer updating the camera&apos;s firmware it is recomended to run the DCM installer from the command line with the following flags:&lt;br/&gt;
+ &lt;span style=&quot; font-weight: bold; font-family:monospace, &apos;Courier New&apos;;&quot;&gt;--ignore-fw-update --silent --no-progress --acceptlicense=yes&lt;/span&gt;
+ &lt;/blockquote&gt;
+&lt;/li&gt;
+&lt;li&gt;
+ RealSenseâ„¢ &lt;a href=&quot;http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/9078/intel_rs_sdk_offline_package_10.0.26.0396.exe&quot;&gt;&lt;span&gt;SDK 2016 R2 runtime&lt;/span&gt;&lt;/a&gt;
+&lt;/li&gt;
+&lt;/ul&gt;
+&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rs/lang/zh_CN.ts b/tracker-rs/lang/zh_CN.ts
index 4f82f60d..dc9b34b5 100644
--- a/tracker-rs/lang/zh_CN.ts
+++ b/tracker-rs/lang/zh_CN.ts
@@ -1,18 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>RSTracker</name>
<message>
- <source>Intel® RealSense™ Runtime Installation</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Installation process failed to start.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Couldn&apos;t initialize RealSense tracking. Please make sure SDK Runtime 2016 R2 is installed.</source>
+ <source>Couldn&apos;t initialize RealSense tracking. Open the tracker settings dialog for links to the camera driver and the SDK.</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -23,30 +15,40 @@
<source>Tracking stopped after another program changed camera streams configuration.</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>RSTrackerMetaData</name>
<message>
- <source>Install Runtime</source>
+ <source>Intel® RealSense™ Technology</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>UIRSControls</name>
<message>
- <source>RealSense 3D Tracker settings</source>
+ <source>Intel RealSense</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>There is no configuration available at the moment.
-
-The application will activate your camera for face tracking by the
-Intel® RealSense™ SDK. By design, the application has no direct access
-to any camera images.
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;
+In order to use this tracker, you need:
-In order to use this tracker, you need a PC equipped with an Intel® RealSense™
-F200 or SR300 camera and the RealSenseâ„¢ SDK 2016 R2 runtime.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Install Runtime</source>
+&lt;ul&gt;
+&lt;li&gt;
+ A PC equipped with an Intel® RealSense™ F200 or SR300 camera
+&lt;/li&gt;
+&lt;li&gt;
+ Depth Camera Manager &lt;a href=&quot;https://www.intel.com/content/www/us/en/download/18309/intel-realsense-depth-camera-manager.html&quot;&gt;&lt;span&gt;driver&lt;/span&gt;&lt;/a&gt;
+ &lt;blockquote&gt;
+ To avoid problems with the DCM installer updating the camera&apos;s firmware it is recomended to run the DCM installer from the command line with the following flags:&lt;br/&gt;
+ &lt;span style=&quot; font-weight: bold; font-family:monospace, &apos;Courier New&apos;;&quot;&gt;--ignore-fw-update --silent --no-progress --acceptlicense=yes&lt;/span&gt;
+ &lt;/blockquote&gt;
+&lt;/li&gt;
+&lt;li&gt;
+ RealSenseâ„¢ &lt;a href=&quot;http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/9078/intel_rs_sdk_offline_package_10.0.26.0396.exe&quot;&gt;&lt;span&gt;SDK 2016 R2 runtime&lt;/span&gt;&lt;/a&gt;
+&lt;/li&gt;
+&lt;/ul&gt;
+&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rs/redist/RS_EULA.rtf b/tracker-rs/redist/RS_EULA.rtf
deleted file mode 100644
index 32a886ea..00000000
--- a/tracker-rs/redist/RS_EULA.rtf
+++ /dev/null
@@ -1,500 +0,0 @@
-{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff1\deff0\stshfdbch0\stshfloch0\stshfhich0\stshfbi0\deflang1033\deflangfe1033\themelang1033\themelangfe1041\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman{\*\falt Times New Roman};}
-{\f1\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial{\*\falt Helvetica};}{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New{\*\falt Courier};}
-{\f3\fbidi \froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol{\*\falt Times New Roman};}{\f10\fbidi \fnil\fcharset2\fprq2{\*\panose 05000000000000000000}Wingdings{\*\falt SymbolProp BT};}
-{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math{\*\falt Calisto MT};}{\f39\fbidi \fswiss\fcharset0\fprq2{\*\panose 00000000000000000000}Tahoma{\*\falt Device Font 10cpi};}
-{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman{\*\falt Times New Roman};}
-{\fdbmajor\f31501\fbidi \fmodern\fcharset128\fprq1{\*\panose 020b0609070205080204}MS Gothic{\*\falt \'82\'6c\'82\'72 \'83\'53\'83\'56\'83\'62\'83\'4e};}{\fhimajor\f31502\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;}
-{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman{\*\falt Times New Roman};}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman{\*\falt Times New Roman};}
-{\fdbminor\f31505\fbidi \froman\fcharset128\fprq1{\*\panose 02020609040205080304}MS Mincho{\*\falt ?l?r ???fc};}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}
-{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman{\*\falt Times New Roman};}{\f54\fbidi \froman\fcharset238\fprq2 Times New Roman CE{\*\falt Times New Roman};}
-{\f55\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr{\*\falt Times New Roman};}{\f57\fbidi \froman\fcharset161\fprq2 Times New Roman Greek{\*\falt Times New Roman};}{\f58\fbidi \froman\fcharset162\fprq2 Times New Roman Tur{\*\falt Times New Roman};}
-{\f59\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew){\*\falt Times New Roman};}{\f60\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic){\*\falt Times New Roman};}
-{\f61\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic{\*\falt Times New Roman};}{\f62\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese){\*\falt Times New Roman};}{\f64\fbidi \fswiss\fcharset238\fprq2 Arial CE{\*\falt Helvetica};}
-{\f65\fbidi \fswiss\fcharset204\fprq2 Arial Cyr{\*\falt Helvetica};}{\f67\fbidi \fswiss\fcharset161\fprq2 Arial Greek{\*\falt Helvetica};}{\f68\fbidi \fswiss\fcharset162\fprq2 Arial Tur{\*\falt Helvetica};}
-{\f69\fbidi \fswiss\fcharset177\fprq2 Arial (Hebrew){\*\falt Helvetica};}{\f70\fbidi \fswiss\fcharset178\fprq2 Arial (Arabic){\*\falt Helvetica};}{\f71\fbidi \fswiss\fcharset186\fprq2 Arial Baltic{\*\falt Helvetica};}
-{\f72\fbidi \fswiss\fcharset163\fprq2 Arial (Vietnamese){\*\falt Helvetica};}{\f74\fbidi \fmodern\fcharset238\fprq1 Courier New CE{\*\falt Courier};}{\f75\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr{\*\falt Courier};}
-{\f77\fbidi \fmodern\fcharset161\fprq1 Courier New Greek{\*\falt Courier};}{\f78\fbidi \fmodern\fcharset162\fprq1 Courier New Tur{\*\falt Courier};}{\f79\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew){\*\falt Courier};}
-{\f80\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic){\*\falt Courier};}{\f81\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic{\*\falt Courier};}{\f82\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese){\*\falt Courier};}
-{\f394\fbidi \froman\fcharset238\fprq2 Cambria Math CE{\*\falt Calisto MT};}{\f395\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr{\*\falt Calisto MT};}{\f397\fbidi \froman\fcharset161\fprq2 Cambria Math Greek{\*\falt Calisto MT};}
-{\f398\fbidi \froman\fcharset162\fprq2 Cambria Math Tur{\*\falt Calisto MT};}{\f401\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic{\*\falt Calisto MT};}{\f402\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese){\*\falt Calisto MT};}
-{\f444\fbidi \fswiss\fcharset238\fprq2 Tahoma CE{\*\falt Device Font 10cpi};}{\f445\fbidi \fswiss\fcharset204\fprq2 Tahoma Cyr{\*\falt Device Font 10cpi};}{\f447\fbidi \fswiss\fcharset161\fprq2 Tahoma Greek{\*\falt Device Font 10cpi};}
-{\f448\fbidi \fswiss\fcharset162\fprq2 Tahoma Tur{\*\falt Device Font 10cpi};}{\f449\fbidi \fswiss\fcharset177\fprq2 Tahoma (Hebrew){\*\falt Device Font 10cpi};}{\f450\fbidi \fswiss\fcharset178\fprq2 Tahoma (Arabic){\*\falt Device Font 10cpi};}
-{\f451\fbidi \fswiss\fcharset186\fprq2 Tahoma Baltic{\*\falt Device Font 10cpi};}{\f452\fbidi \fswiss\fcharset163\fprq2 Tahoma (Vietnamese){\*\falt Device Font 10cpi};}{\f453\fbidi \fswiss\fcharset222\fprq2 Tahoma (Thai){\*\falt Device Font 10cpi};}
-{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE{\*\falt Times New Roman};}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr{\*\falt Times New Roman};}
-{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek{\*\falt Times New Roman};}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur{\*\falt Times New Roman};}
-{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew){\*\falt Times New Roman};}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic){\*\falt Times New Roman};}
-{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic{\*\falt Times New Roman};}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese){\*\falt Times New Roman};}
-{\fdbmajor\f31520\fbidi \fmodern\fcharset0\fprq1 MS Gothic Western{\*\falt \'82\'6c\'82\'72 \'83\'53\'83\'56\'83\'62\'83\'4e};}{\fdbmajor\f31518\fbidi \fmodern\fcharset238\fprq1 MS Gothic CE{\*\falt \'82\'6c\'82\'72 \'83\'53\'83\'56\'83\'62\'83\'4e};}
-{\fdbmajor\f31519\fbidi \fmodern\fcharset204\fprq1 MS Gothic Cyr{\*\falt \'82\'6c\'82\'72 \'83\'53\'83\'56\'83\'62\'83\'4e};}{\fdbmajor\f31521\fbidi \fmodern\fcharset161\fprq1 MS Gothic Greek{\*\falt \'82\'6c\'82\'72 \'83\'53\'83\'56\'83\'62\'83\'4e};}
-{\fdbmajor\f31522\fbidi \fmodern\fcharset162\fprq1 MS Gothic Tur{\*\falt \'82\'6c\'82\'72 \'83\'53\'83\'56\'83\'62\'83\'4e};}{\fdbmajor\f31525\fbidi \fmodern\fcharset186\fprq1 MS Gothic Baltic{\*\falt \'82\'6c\'82\'72 \'83\'53\'83\'56\'83\'62\'83\'4e};}
-{\fhimajor\f31528\fbidi \froman\fcharset238\fprq2 Cambria CE;}{\fhimajor\f31529\fbidi \froman\fcharset204\fprq2 Cambria Cyr;}{\fhimajor\f31531\fbidi \froman\fcharset161\fprq2 Cambria Greek;}{\fhimajor\f31532\fbidi \froman\fcharset162\fprq2 Cambria Tur;}
-{\fhimajor\f31535\fbidi \froman\fcharset186\fprq2 Cambria Baltic;}{\fhimajor\f31536\fbidi \froman\fcharset163\fprq2 Cambria (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE{\*\falt Times New Roman};}
-{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr{\*\falt Times New Roman};}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek{\*\falt Times New Roman};}
-{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur{\*\falt Times New Roman};}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew){\*\falt Times New Roman};}
-{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic){\*\falt Times New Roman};}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic{\*\falt Times New Roman};}
-{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese){\*\falt Times New Roman};}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE{\*\falt Times New Roman};}
-{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr{\*\falt Times New Roman};}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek{\*\falt Times New Roman};}
-{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur{\*\falt Times New Roman};}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew){\*\falt Times New Roman};}
-{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic){\*\falt Times New Roman};}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic{\*\falt Times New Roman};}
-{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese){\*\falt Times New Roman};}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}
-{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}
-{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE{\*\falt Times New Roman};}
-{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr{\*\falt Times New Roman};}{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek{\*\falt Times New Roman};}
-{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur{\*\falt Times New Roman};}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew){\*\falt Times New Roman};}
-{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic){\*\falt Times New Roman};}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic{\*\falt Times New Roman};}
-{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese){\*\falt Times New Roman};}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;
-\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;
-\cfollowedhyperlink\ctint255\cshade255\red128\green0\blue128;}{\*\defchp \fs22 }{\*\defpap \ql \li0\ri0\sa200\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{
-\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 \sqformat \spriority0 \styrsid14303487 Normal;}{\*\cs10
-\additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
-\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa200\sl276\slmult1
-\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{
-\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af2\afs20\alang1025 \ltrch\fcs0 \f2\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext15 \slink16 \styrsid14303487 Plain Text;}{\*
-\cs16 \additive \rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20 \sbasedon10 \slink15 \slocked \styrsid14303487 Plain Text Char;}{\s17\qj \li0\ri0\sb120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025
-\ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext17 \slink18 \styrsid14303487 Body Text 3;}{\*\cs18 \additive \rtlch\fcs1 \af1\afs16 \ltrch\fcs0 \f1\fs16 \sbasedon10 \slink17 \slocked \ssemihidden \styrsid14303487
-Body Text 3 Char;}{\*\cs19 \additive \rtlch\fcs1 \af0\afs16 \ltrch\fcs0 \fs16 \sbasedon10 \ssemihidden \styrsid14303487 annotation reference;}{\s20\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1
-\af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext20 \slink21 \ssemihidden \styrsid14303487 annotation text;}{\*\cs21 \additive \rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20
-\sbasedon10 \slink20 \slocked \ssemihidden \styrsid14303487 Comment Text Char;}{\s22\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af39\afs16\alang1025 \ltrch\fcs0
-\f39\fs16\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext22 \slink23 \ssemihidden \styrsid14303487 Balloon Text;}{\*\cs23 \additive \rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16
-\sbasedon10 \slink22 \slocked \ssemihidden \styrsid14303487 Balloon Text Char;}{\s24\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af1\afs20\alang1025 \ltrch\fcs0
-\b\f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon20 \snext20 \slink25 \ssemihidden \styrsid6379084 annotation subject;}{\*\cs25 \additive \rtlch\fcs1 \ab\af1\afs20 \ltrch\fcs0 \b\f1\fs20
-\sbasedon21 \slink24 \slocked \ssemihidden \styrsid14303487 Comment Subject Char;}{\*\cs26 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf2 \sbasedon10 \styrsid2040439 Hyperlink;}{
-\s27\ql \li720\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033
-\sbasedon0 \snext27 \sqformat \spriority34 \styrsid4739295 List Paragraph;}{\s28\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0
-\f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext28 \slink29 \sunhideused \styrsid5132089 header;}{\*\cs29 \additive \rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20 \sbasedon10 \slink28 \slocked \styrsid5132089 Header Char;}{
-\s30\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033
-\sbasedon0 \snext30 \slink31 \sunhideused \styrsid5132089 footer;}{\*\cs31 \additive \rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20 \sbasedon10 \slink30 \slocked \styrsid5132089 Footer Char;}{
-\s32\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext32 \shidden \ssemihidden \styrsid11285450 Revision;}{\*
-\cs33 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf17 \sbasedon10 \styrsid8743288 FollowedHyperlink;}{\s34\qj \fi720\li0\ri0\sa240\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0
-\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon37 \snext34 \spriority0 \styrsid13708007 NumContHalf;}{\s35\qj \fi432\li0\ri0\nowidctlpar\tx792\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs18\alang1025 \ltrch\fcs0
-\fs18\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext34 \slink36 \spriority0 \styrsid13708007 Legal2_L3;}{\*\cs36 \additive \rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \fs18 \sbasedon10 \slink35 \slocked \spriority0 \styrsid13708007
-Legal2_L3 Char;}{\s37\ql \li0\ri0\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033
-\sbasedon0 \snext37 \slink38 \styrsid13708007 Body Text;}{\*\cs38 \additive \rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20 \sbasedon10 \slink37 \slocked \styrsid13708007 Body Text Char;}}{\*\listtable{\list\listtemplateid943886856{\listlevel\levelnfc0
-\levelnfcn0\leveljc0\leveljcn0\levelfollow1\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow1\levelstartat1\levelspace0
-\levelindent0{\leveltext\'05\'00.\'01. ;}{\levelnumbers\'01\'03;}\rtlch\fcs1 \af0 \ltrch\fcs0 \b\fbias0 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'06\'00.\'01.\'02.;}{\levelnumbers
-\'01\'03\'05;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-504\li1224\lin1224 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'08\'00.\'01.\'02.\'03.;}{\levelnumbers\'01\'03\'05\'07;}
-\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-648\li1728\lin1728 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0a\'00.\'01.\'02.\'03.\'04.;}{\levelnumbers\'01\'03\'05\'07\'09;}\rtlch\fcs1
-\af0 \ltrch\fcs0 \fbias0 \fi-792\li2232\lin2232 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0c\'00.\'01.\'02.\'03.\'04.\'05.;}{\levelnumbers\'01\'03\'05\'07\'09\'0b;}\rtlch\fcs1
-\af0 \ltrch\fcs0 \fbias0 \fi-936\li2736\lin2736 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0e\'00.\'01.\'02.\'03.\'04.\'05.\'06.;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d;}
-\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1080\li3240\lin3240 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'10\'00.\'01.\'02.\'03.\'04.\'05.\'06.\'07.;}{\levelnumbers
-\'01\'03\'05\'07\'09\'0b\'0d\'0f;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1224\li3744\lin3744 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext
-\'12\'00.\'01.\'02.\'03.\'04.\'05.\'06.\'07.\'08.;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d\'0f\'11;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1440\li4320\lin4320 }{\listname ;}\listid329989484}{\list\listtemplateid68476100{\listlevel\levelnfc0
-\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af0 \ltrch\fcs0 \b0\i0\fbias0 \fi-360\li360\jclisttab\tx360\lin360 }{\listlevel\levelnfc3\levelnfcn3\leveljc0
-\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-360\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0
-\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af1\afs20 \ltrch\fcs0 \b0\i0\f1\fs20\fbias0 \fi-360\li1080\jclisttab\tx1440\lin1080 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0
-\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc5\levelnfcn5\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0
-\levelindent0{\leveltext\'01\'04;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-360\li1800\jclisttab\tx2160\lin1800 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext
-\'00;}{\levelnumbers;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1080\li4680\jclisttab\tx4680\lin4680 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'00;}{\levelnumbers;}\rtlch\fcs1
-\af0 \ltrch\fcs0 \fbias0 \fi-1440\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'00;}{\levelnumbers;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0
-\fi-1440\li6480\jclisttab\tx6480\lin6480 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'00;}{\levelnumbers;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1440\li7200
-\jclisttab\tx7200\lin7200 }{\listname ;}\listid441388039}{\list\listtemplateid805977356\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689
-\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li1080\lin1080 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0
-\fi-360\li1800\lin1800 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li2520\lin2520 }{\listlevel
-\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li3240\lin3240 }{\listlevel\levelnfc23\levelnfcn23\leveljc0
-\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li3960\lin3960 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1
-\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li4680\lin4680 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0
-\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li5400\lin5400 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
-\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li6120\lin6120 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693
-\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li6840\lin6840 }{\listname ;}\listid485559603}{\list\listtemplateid-68014412\listhybrid{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext
-\leveltemplateid2135208358\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-720\li720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698713
-\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li1080\lin1080 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid-388622234\'03(\'02);}{\levelnumbers
-\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-720\li2520\lin2520 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid170695360\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0
-\ltrch\fcs0 \fbias0 \fi-360\li2520\lin2520 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0
-\fi-360\li3240\lin3240 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0
-\fi-180\li3960\lin3960 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0
-\fi-360\li4680\lin4680 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0
-\fi-360\li5400\lin5400 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0
-\fi-180\li6120\lin6120 }{\listname ;}\listid544567740}{\list\listtemplateid410972382\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689
-\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li3060\lin3060 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0
-\fi-360\li3780\lin3780 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li4500\lin4500 }{\listlevel
-\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li5220\lin5220 }{\listlevel\levelnfc23\levelnfcn23\leveljc0
-\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li5940\lin5940 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1
-\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li6660\lin6660 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0
-\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li7380\lin7380 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
-\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li8100\lin8100 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693
-\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li8820\lin8820 }{\listname ;}\listid707484521}{\list\listtemplateid-21853232\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext
-\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691
-\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0
-\fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li5040\lin5040 }{\listlevel
-\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0
-\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li6480\lin6480 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1
-\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 \fi-360\li7200\lin7200 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0
-\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0 \fi-360\li7920\lin7920 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
-\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0 \fi-360\li8640\lin8640 }{\listname ;}\listid824903796}{\list\listtemplateid68476100{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0
-{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af0 \ltrch\fcs0 \b0\i0\fbias0 \fi-360\li360\jclisttab\tx360\lin360 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext
-\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-360\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}
-\rtlch\fcs1 \ab0\ai0\af1\afs20 \ltrch\fcs0 \b0\i0\f1\fs20\fbias0 \fi-360\li1080\jclisttab\tx1440\lin1080 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03);}{\levelnumbers\'01;}
-\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc5\levelnfcn5\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\'04;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0
-\fbias0 \fi-360\li1800\jclisttab\tx2160\lin1800 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'00;}{\levelnumbers;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1080\li4680
-\jclisttab\tx4680\lin4680 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'00;}{\levelnumbers;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1440\li5760\jclisttab\tx5760\lin5760 }
-{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'00;}{\levelnumbers;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1440\li6480\jclisttab\tx6480\lin6480 }{\listlevel\levelnfc255\levelnfcn255
-\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'00;}{\levelnumbers;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1440\li7200\jclisttab\tx7200\lin7200 }{\listname ;}\listid1644384124}}{\*\listoverridetable
-{\listoverride\listid1644384124\listoverridecount0\ls1}{\listoverride\listid441388039\listoverridecount0\ls2}{\listoverride\listid544567740\listoverridecount0\ls3}{\listoverride\listid824903796\listoverridecount0\ls4}{\listoverride\listid707484521
-\listoverridecount0\ls5}{\listoverride\listid329989484\listoverridecount0\ls6}{\listoverride\listid485559603\listoverridecount0\ls7}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp
-\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}{\*\rsidtbl \rsid3871\rsid66918\rsid69069\rsid75135\rsid85711\rsid137108\rsid137924\rsid146312\rsid151239\rsid153533
-\rsid156990\rsid160081\rsid198028\rsid202694\rsid207825\rsid211750\rsid282622\rsid282972\rsid329177\rsid329238\rsid342657\rsid356700\rsid357193\rsid358024\rsid359415\rsid400567\rsid407655\rsid419146\rsid421204\rsid462293\rsid466682\rsid475959\rsid479602
-\rsid486784\rsid539942\rsid545232\rsid552669\rsid590787\rsid601607\rsid663277\rsid679090\rsid724444\rsid725204\rsid741488\rsid743173\rsid749205\rsid788341\rsid795074\rsid806285\rsid872520\rsid873709\rsid881679\rsid882529\rsid919535\rsid923718\rsid929135
-\rsid931635\rsid931852\rsid937482\rsid984611\rsid994997\rsid997207\rsid1000228\rsid1004679\rsid1007986\rsid1009136\rsid1014919\rsid1048643\rsid1061385\rsid1070072\rsid1072521\rsid1074905\rsid1078384\rsid1079894\rsid1124516\rsid1128701\rsid1131491
-\rsid1135412\rsid1144547\rsid1145067\rsid1180798\rsid1183572\rsid1191146\rsid1192721\rsid1201656\rsid1201787\rsid1206112\rsid1263777\rsid1265155\rsid1265226\rsid1277387\rsid1311358\rsid1311459\rsid1324997\rsid1326041\rsid1326225\rsid1334095\rsid1339638
-\rsid1384919\rsid1403165\rsid1404231\rsid1448795\rsid1452695\rsid1458229\rsid1508611\rsid1518169\rsid1533843\rsid1535856\rsid1536096\rsid1579768\rsid1583847\rsid1589961\rsid1590400\rsid1591787\rsid1645745\rsid1647912\rsid1652409\rsid1659177\rsid1663057
-\rsid1715548\rsid1719810\rsid1723148\rsid1724567\rsid1730238\rsid1788068\rsid1799579\rsid1839848\rsid1843572\rsid1849284\rsid1850865\rsid1855001\rsid1859881\rsid1861632\rsid1902002\rsid1911019\rsid1917646\rsid1921655\rsid1929189\rsid1932971\rsid1970675
-\rsid1974342\rsid1978318\rsid1980744\rsid1984201\rsid1987795\rsid1993852\rsid2037336\rsid2040439\rsid2045652\rsid2050995\rsid2052911\rsid2059216\rsid2059491\rsid2099229\rsid2099795\rsid2116613\rsid2120571\rsid2125148\rsid2163674\rsid2166864\rsid2191393
-\rsid2193630\rsid2237684\rsid2241656\rsid2295773\rsid2306835\rsid2310863\rsid2315353\rsid2378398\rsid2442612\rsid2450450\rsid2450834\rsid2454708\rsid2492665\rsid2499706\rsid2501655\rsid2502163\rsid2565397\rsid2574711\rsid2576185\rsid2579849\rsid2647495
-\rsid2647783\rsid2699092\rsid2699588\rsid2704767\rsid2708742\rsid2756841\rsid2773985\rsid2775431\rsid2829476\rsid2832819\rsid2839464\rsid2843459\rsid2846564\rsid2891246\rsid2893631\rsid2898001\rsid2907312\rsid2907705\rsid2913731\rsid2957351\rsid2958229
-\rsid2958727\rsid2963278\rsid2966033\rsid2970760\rsid2972553\rsid2979196\rsid3015193\rsid3016305\rsid3016777\rsid3031328\rsid3089601\rsid3091420\rsid3092866\rsid3106341\rsid3110018\rsid3146275\rsid3160546\rsid3175081\rsid3225454\rsid3229104\rsid3232943
-\rsid3243252\rsid3294150\rsid3298073\rsid3305295\rsid3342712\rsid3342735\rsid3351520\rsid3351846\rsid3357183\rsid3357351\rsid3371788\rsid3421912\rsid3435427\rsid3436597\rsid3436600\rsid3437634\rsid3438612\rsid3475774\rsid3489087\rsid3493982\rsid3500008
-\rsid3562116\rsid3627601\rsid3630136\rsid3690006\rsid3741102\rsid3759601\rsid3766682\rsid3802171\rsid3821256\rsid3827201\rsid3827912\rsid3830311\rsid3868852\rsid3886264\rsid3896708\rsid3897955\rsid3941237\rsid3948040\rsid3948373\rsid3957578\rsid4014069
-\rsid4020181\rsid4072795\rsid4078393\rsid4087840\rsid4091441\rsid4092900\rsid4129877\rsid4135720\rsid4145682\rsid4148068\rsid4148592\rsid4150673\rsid4198904\rsid4206281\rsid4209321\rsid4209903\rsid4259933\rsid4261255\rsid4262358\rsid4267449\rsid4280130
-\rsid4325530\rsid4326266\rsid4336219\rsid4343576\rsid4351390\rsid4355301\rsid4399195\rsid4399851\rsid4412773\rsid4421167\rsid4471713\rsid4528410\rsid4539267\rsid4593901\rsid4609541\rsid4614340\rsid4618426\rsid4660003\rsid4667353\rsid4669289\rsid4671705
-\rsid4681302\rsid4681633\rsid4719347\rsid4721462\rsid4722091\rsid4725804\rsid4739295\rsid4742942\rsid4747003\rsid4812494\rsid4850863\rsid4857766\rsid4861374\rsid4875357\rsid4881856\rsid4882009\rsid4937213\rsid5010760\rsid5046453\rsid5057819\rsid5062679
-\rsid5068889\rsid5077449\rsid5127585\rsid5132089\rsid5136203\rsid5184028\rsid5184043\rsid5192747\rsid5196999\rsid5249012\rsid5249549\rsid5249692\rsid5261934\rsid5321807\rsid5334295\rsid5336741\rsid5339812\rsid5381374\rsid5399223\rsid5406010\rsid5457933
-\rsid5463243\rsid5466539\rsid5517083\rsid5523111\rsid5573634\rsid5585916\rsid5600085\rsid5648409\rsid5719086\rsid5720834\rsid5779133\rsid5785533\rsid5849510\rsid5900528\rsid5904752\rsid5906377\rsid5906895\rsid5918120\rsid5921096\rsid5923997\rsid5966646
-\rsid5973577\rsid5996449\rsid6060820\rsid6105594\rsid6108837\rsid6110785\rsid6116107\rsid6120275\rsid6161255\rsid6166376\rsid6175590\rsid6180039\rsid6184512\rsid6188393\rsid6225990\rsid6241290\rsid6241409\rsid6246033\rsid6296810\rsid6304626\rsid6320405
-\rsid6324083\rsid6357750\rsid6365415\rsid6375652\rsid6376513\rsid6379084\rsid6442947\rsid6492190\rsid6494652\rsid6505372\rsid6566187\rsid6570264\rsid6572102\rsid6574155\rsid6578316\rsid6620599\rsid6632431\rsid6639695\rsid6649863\rsid6692624\rsid6696349
-\rsid6702661\rsid6709371\rsid6710559\rsid6758971\rsid6764647\rsid6776703\rsid6828498\rsid6829884\rsid6839304\rsid6843953\rsid6847452\rsid6847679\rsid6946993\rsid6947807\rsid6954080\rsid6961984\rsid6978801\rsid7017500\rsid7017611\rsid7025681\rsid7040760
-\rsid7080275\rsid7091983\rsid7164646\rsid7219855\rsid7228892\rsid7232980\rsid7238677\rsid7297671\rsid7417151\rsid7435347\rsid7437320\rsid7487751\rsid7492751\rsid7548303\rsid7552304\rsid7552534\rsid7556315\rsid7566001\rsid7567592\rsid7602704\rsid7609841
-\rsid7680281\rsid7691720\rsid7738511\rsid7740039\rsid7745287\rsid7760751\rsid7762900\rsid7765676\rsid7808752\rsid7809112\rsid7824898\rsid7872716\rsid7879552\rsid7886639\rsid7889606\rsid7937530\rsid7941117\rsid7945947\rsid7997007\rsid8000836\rsid8003299
-\rsid8005617\rsid8012256\rsid8015865\rsid8064933\rsid8069222\rsid8137040\rsid8193455\rsid8195828\rsid8204336\rsid8210438\rsid8214866\rsid8264069\rsid8265565\rsid8277033\rsid8280527\rsid8282014\rsid8283196\rsid8287800\rsid8342270\rsid8342360\rsid8344337
-\rsid8389295\rsid8391026\rsid8392242\rsid8404479\rsid8410773\rsid8470179\rsid8475748\rsid8478282\rsid8481775\rsid8606381\rsid8615804\rsid8658777\rsid8674604\rsid8722032\rsid8728917\rsid8743086\rsid8743288\rsid8782913\rsid8788954\rsid8790915\rsid8800875
-\rsid8801975\rsid8803952\rsid8810494\rsid8813984\rsid8855466\rsid8857770\rsid8865314\rsid8866793\rsid8874444\rsid8875224\rsid8877050\rsid8913393\rsid8917913\rsid8925442\rsid8925736\rsid8979401\rsid8994296\rsid9044499\rsid9063728\rsid9064095\rsid9113502
-\rsid9198675\rsid9200129\rsid9242983\rsid9269061\rsid9322402\rsid9326521\rsid9377820\rsid9386310\rsid9396731\rsid9452180\rsid9506122\rsid9520851\rsid9573563\rsid9577285\rsid9589296\rsid9599089\rsid9635079\rsid9635423\rsid9651283\rsid9701569\rsid9703158
-\rsid9720361\rsid9725188\rsid9769754\rsid9784732\rsid9785035\rsid9786746\rsid9843182\rsid9853330\rsid9910053\rsid9916841\rsid9917054\rsid9919425\rsid9923618\rsid9964852\rsid9979160\rsid9983406\rsid9984403\rsid10030620\rsid10032131\rsid10035755
-\rsid10048473\rsid10049771\rsid10049812\rsid10096343\rsid10100797\rsid10107635\rsid10116690\rsid10120235\rsid10160035\rsid10160330\rsid10172449\rsid10174303\rsid10174780\rsid10228144\rsid10228283\rsid10251768\rsid10253899\rsid10293682\rsid10309505
-\rsid10355440\rsid10357127\rsid10361190\rsid10365363\rsid10381244\rsid10384493\rsid10422128\rsid10430016\rsid10440933\rsid10441586\rsid10445653\rsid10450650\rsid10490382\rsid10503182\rsid10505640\rsid10562273\rsid10580548\rsid10620395\rsid10624800
-\rsid10631222\rsid10633296\rsid10645274\rsid10694001\rsid10750524\rsid10764857\rsid10775781\rsid10778508\rsid10779733\rsid10819658\rsid10824371\rsid10824752\rsid10834423\rsid10839371\rsid10844386\rsid10897936\rsid10948611\rsid10956890\rsid10967199
-\rsid10971714\rsid10974324\rsid11021468\rsid11026855\rsid11037309\rsid11098641\rsid11102015\rsid11102226\rsid11107711\rsid11108046\rsid11108116\rsid11143101\rsid11147018\rsid11164224\rsid11167741\rsid11207137\rsid11208016\rsid11208274\rsid11219228
-\rsid11220711\rsid11225853\rsid11239254\rsid11279303\rsid11285450\rsid11297405\rsid11359444\rsid11366824\rsid11413850\rsid11426186\rsid11435273\rsid11469276\rsid11473058\rsid11481853\rsid11486480\rsid11488592\rsid11497082\rsid11500361\rsid11501403
-\rsid11539458\rsid11548288\rsid11549742\rsid11552981\rsid11556993\rsid11623189\rsid11623918\rsid11628133\rsid11676467\rsid11680559\rsid11686843\rsid11693171\rsid11732006\rsid11756279\rsid11802612\rsid11806746\rsid11811571\rsid11819056\rsid11822988
-\rsid11824911\rsid11867640\rsid11875137\rsid11877716\rsid11878063\rsid11882961\rsid11885937\rsid11886111\rsid11888686\rsid11891132\rsid11935012\rsid11942024\rsid11952157\rsid11953150\rsid12004670\rsid12010873\rsid12011804\rsid12012236\rsid12012255
-\rsid12014608\rsid12014819\rsid12015112\rsid12024204\rsid12062377\rsid12062779\rsid12068607\rsid12069495\rsid12085353\rsid12130863\rsid12256312\rsid12256523\rsid12257237\rsid12260293\rsid12261080\rsid12271359\rsid12274916\rsid12275464\rsid12325757
-\rsid12326504\rsid12332903\rsid12344690\rsid12414653\rsid12460318\rsid12463827\rsid12471504\rsid12477228\rsid12478724\rsid12537482\rsid12537962\rsid12549865\rsid12608594\rsid12651712\rsid12672852\rsid12731948\rsid12743524\rsid12745899\rsid12786122
-\rsid12871316\rsid12874081\rsid12876014\rsid12922244\rsid12922344\rsid12927143\rsid12943060\rsid12983899\rsid12988002\rsid12996964\rsid13004370\rsid13042253\rsid13047288\rsid13052975\rsid13056645\rsid13067301\rsid13067639\rsid13069160\rsid13069410
-\rsid13071193\rsid13112899\rsid13115859\rsid13119444\rsid13126250\rsid13130609\rsid13176295\rsid13176992\rsid13181744\rsid13183122\rsid13186882\rsid13196225\rsid13252463\rsid13253372\rsid13322465\rsid13323508\rsid13323817\rsid13324415\rsid13328507
-\rsid13330894\rsid13331944\rsid13372189\rsid13372908\rsid13375702\rsid13453474\rsid13454846\rsid13462347\rsid13463293\rsid13514846\rsid13516329\rsid13523013\rsid13524957\rsid13528174\rsid13570620\rsid13586713\rsid13596148\rsid13663127\rsid13707473
-\rsid13708007\rsid13713615\rsid13719504\rsid13720946\rsid13721970\rsid13722257\rsid13722272\rsid13793230\rsid13829090\rsid13838324\rsid13841566\rsid13844380\rsid13847347\rsid13910796\rsid13921385\rsid13971807\rsid13973573\rsid13973803\rsid13975176
-\rsid13981935\rsid14031899\rsid14033460\rsid14038929\rsid14040714\rsid14043563\rsid14099381\rsid14117694\rsid14117827\rsid14122553\rsid14228219\rsid14235907\rsid14248592\rsid14249138\rsid14249383\rsid14252609\rsid14294395\rsid14301785\rsid14303487
-\rsid14304947\rsid14307521\rsid14354492\rsid14365919\rsid14365934\rsid14366778\rsid14368065\rsid14374892\rsid14375301\rsid14380022\rsid14383422\rsid14421357\rsid14430586\rsid14447730\rsid14496443\rsid14503935\rsid14512842\rsid14514999\rsid14552199
-\rsid14552357\rsid14564818\rsid14565222\rsid14571550\rsid14683126\rsid14683752\rsid14699073\rsid14699850\rsid14700462\rsid14756429\rsid14759314\rsid14830500\rsid14836794\rsid14881074\rsid14886027\rsid14900659\rsid14942810\rsid14960174\rsid14960850
-\rsid14963162\rsid15008019\rsid15018651\rsid15024497\rsid15036140\rsid15073959\rsid15092912\rsid15094380\rsid15148912\rsid15164301\rsid15220177\rsid15220973\rsid15222474\rsid15280407\rsid15290979\rsid15293105\rsid15299866\rsid15301288\rsid15350837
-\rsid15356206\rsid15427037\rsid15431050\rsid15432757\rsid15470649\rsid15480824\rsid15494447\rsid15495454\rsid15547305\rsid15552800\rsid15559163\rsid15563391\rsid15605074\rsid15608682\rsid15615264\rsid15627743\rsid15663597\rsid15693844\rsid15728698
-\rsid15729373\rsid15739697\rsid15746936\rsid15749197\rsid15795100\rsid15795738\rsid15807911\rsid15879054\rsid15930603\rsid15934083\rsid15934887\rsid15945311\rsid15993324\rsid16018488\rsid16058977\rsid16068065\rsid16080106\rsid16127415\rsid16133021
-\rsid16143716\rsid16189443\rsid16199918\rsid16212906\rsid16261946\rsid16271718\rsid16278957\rsid16279434\rsid16282827\rsid16320177\rsid16327791\rsid16334065\rsid16338921\rsid16387018\rsid16455427\rsid16461674\rsid16463549\rsid16465145\rsid16468932
-\rsid16470481\rsid16470862\rsid16472097\rsid16477802\rsid16516593\rsid16538441\rsid16538957\rsid16588102\rsid16594130\rsid16595738\rsid16601385\rsid16608360\rsid16667692\rsid16716458\rsid16719998\rsid16728336\rsid16734318\rsid16741750}{\mmathPr
-\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\creatim\yr2016\mo4\dy26\hr17\min3}{\revtim\yr2016\mo4\dy26\hr17\min3}{\version1}{\edmins0}{\nofpages4}{\nofwords2441}
-{\nofchars13920}{\nofcharsws16329}{\vern57441}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw12240\paperh15840\margl1319\margr1319\margt1134\margb1134\gutter0\ltrsect
-\widowctrl\ftnbj\aenddoc\hyphhotz425\trackmoves0\trackformatting1\donotembedsysfont0\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml0\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors0
-\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\hyphcaps0\formshade\horzdoc\dghspace180\dgvspace180\dghorigin1701\dgvorigin1984\dghshow0\dgvshow0
-\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\rempersonalinfo\allowfieldendsel
-\wrppunct\asianbrkrule\rsidroot2378398\newtblstyruls\nogrowautofit\remdttm\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal
-\nouicompat \fet0{\*\wgrffmtfilter 013f}\nofeaturethrottle1\ilfomacatclnup0{\*\ftnsep \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid5132089 \rtlch\fcs1 \af1\afs20\alang1025
-\ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1 \ltrch\fcs0 \insrsid13713615 \chftnsep
-\par }}{\*\ftnsepc \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid5132089 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {
-\rtlch\fcs1 \af1 \ltrch\fcs0 \insrsid13713615 \chftnsepc
-\par }}{\*\ftncn \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1
-\ltrch\fcs0 \insrsid13713615
-\par }}{\*\aftnsep \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid5132089 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {
-\rtlch\fcs1 \af1 \ltrch\fcs0 \insrsid13713615 \chftnsep
-\par }}{\*\aftnsepc \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid5132089 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {
-\rtlch\fcs1 \af1 \ltrch\fcs0 \insrsid13713615 \chftnsepc
-\par }}{\*\aftncn \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1
-\ltrch\fcs0 \insrsid13713615
-\par }}\ltrpar \sectd \ltrsect\linex0\endnhere\sectdefaultcl\sectrsid14303487\sftnbj {\headerl \ltrpar \pard\plain \ltrpar\s28\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1
-\af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1 \ltrch\fcs0 \insrsid2116613
-\par }}{\headerr \ltrpar \pard\plain \ltrpar\s28\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033
-{\rtlch\fcs1 \af1 \ltrch\fcs0 \insrsid2116613
-\par }}{\footerl \ltrpar \pard\plain \ltrpar\s30\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033
-{\rtlch\fcs1 \af1 \ltrch\fcs0 \insrsid2116613
-\par }}{\footerr \ltrpar \pard\plain \ltrpar\s30\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033
-{\rtlch\fcs1 \af1 \ltrch\fcs0 \insrsid2116613
-\par }}{\headerf \ltrpar \pard\plain \ltrpar\s28\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033
-{\rtlch\fcs1 \af1 \ltrch\fcs0 \insrsid2116613
-\par }}{\footerf \ltrpar \pard\plain \ltrpar\s30\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033
-{\rtlch\fcs1 \af1 \ltrch\fcs0 \insrsid2116613
-\par }}{\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}
-{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8
-\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\qc \li0\ri0\widctlpar\wrapdefault\faauto\rin0\lin0\itap0\pararsid13119444 \rtlch\fcs1
-\af1\afs20\alang1025 \ltrch\fcs0 \f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af1 \ltrch\fcs0 \b\cf1\insrsid13119444 END USER LICENSE AGREEMENT
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 For
-\par Intel\'ae REALSENSE\'99 sdk runtime
-\par April 2015 Version
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 IMPORTANT - READ BEFORE COPYING, INSTALLING OR USING the Software
-\par }\pard \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\faauto\rin0\lin0\itap0\pararsid13119444 {\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
-\par Do not copy, distribute, install or use this software, binary files, and any associated documentation (collectively, the "}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \ul\cf1\insrsid13119444 Software}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
-") provided by Intel Corporation or its majority-owned subsidiaries (\'93Intel\'94) until you have carefully read the following term
-s and conditions. By, copying, distributing, installing or using the Software, you agree to the terms of this license agreement (this \'93}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \ul\cf1\insrsid13119444 License Agreement}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0
-\cf1\insrsid13119444 \'94). If you do not wish to agree, do not copy, distribute, install or use the Software.
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444
-\par }\pard \ltrpar\qj \li0\ri0\widctlpar\wrapdefault\faauto\rin0\lin0\itap0\pararsid13119444 {\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 1.\tab }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\ul\cf1\insrsid13119444 license grant AND RESTRICTIONS}{
-\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 .}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
- Subject to the terms and conditions of this License Agreement, Intel hereby grants you, during the term of this License Agreement, a non-transferable, non-exclusive, personal, non-sublicensable, limited right and license: (A) under Intel\rquote
-s copyrights, to reproduce only one copy per license and use the Software on your computer for your personal, internal use; and (B) under Intel\rquote
-s Licensed Patent Claims (defined below) to: (1) make copies of the Software internally only; and (2) use t
-he Software internally only. You may also make one copy of the Software solely for backup purposes. If you are an entity, you will designate one individual within your organization to have the sole right to use the Software in the manner provided above.
-
-\par
-\par Except as expressly permitted above, you will not copy, modify, rent, sell, distribute or otherwise transfer or assign any part of the Software or this License Agreement to any third party, and you agree to prevent unauthorized copying of the Software. U
-nless expressly permitted above, you will not and will not allow any third party to (1) use or copy the Software, (2) modify, adapt, enhance, reverse engineer, decompile, disassemble, change or create derivative works from the Software or otherwise attemp
-t
- to discover the source code for, or any trade secrets related to, the Software except and only to the extent that applicable law expressly permits the activity notwithstanding this limitation; or (3) use the Software to process the data of third parties.
- You will not sublicense or permit simultaneous use of the Software by more than one user. \'93Licensed Patent Claims\'94 means the claims of Intel\rquote
-s patents that are necessarily and directly infringed by the reproduction of the Software that is authorized in this Section 1, when that Software is in its unmodified form as delivered by Intel to you and not modified or combined with anything else.
-
-\par
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 MEDIA FORMAT CODECS AND DIGITAL RIGHTS MANAGEMENT}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444 .\~ You acknowledge and agree that your use of the Materials or distri
-bution of the Materials with Your Product as permitted by this license may require you to procure license(s) from one or more third parties that may hold intellectual property rights applicable to any media decoding, encoding or transcoding technology (su
-c
-h as, for example, through use of an audio or video codec) and/or digital rights management capabilities of the Materials, if any. Should any such additional licenses be required, You are solely responsible for obtaining any such licenses and agree to ob
-tain any such licenses at Your own expense.
-\par
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 2.\tab }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\ul\cf1\insrsid13119444 For Third party Software}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 .}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0
-\cf1\insrsid13119444 Certain programs and or files may be included with the Software that are provided and licensed by third parties other than Intel (\'93}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \ul\cf1\insrsid13119444 Third Party Software}{\rtlch\fcs1
-\af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444 \'94), and may include Open Source
- Code (defined below). Your rights to use the Third Party Software are governed by the license agreements that accompany such components. Intel does not warrant such Third Party Software in any way and assumes no liability for your use of the Third Party
-Software. You are subject to the terms of their license agreements if you use the Third Party Software. \'93}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 Open Source Code}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444 \'94
- means any software that requires as a condition of use, modification and/or distribution of such software that such software or othe
-r software incorporated into, derived from or distributed with such software (a) be disclosed or distributed in source code form; or (b) be licensed by the user to third parties for the purpose of making and/or distributing derivative works; or (c) be red
-i
-stributable at no charge. Open Source Software includes, without limitation, software licensed or distributed under any of the following licenses or distribution models, or licenses or distribution models substantially similar to any of the following: (a
-) GNU\rquote
-s General Public License (GPL) or Lesser/Library GPL (LGPL), (b) the Artistic License (e.g., PERL), (c) the Mozilla Public License, (d) the Netscape Public License, (e) the Sun Community Source License (SCSL), (f) the Sun Industry Source License (SISL
-), (g) the Apache Software license and (h) the Common Public License (CPL).
-\par
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 3.\tab }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\ul\cf1\insrsid13119444 NO Other Rights.}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
- Specifically, except for the licenses that Intel expressly granted You in Section 1, Intel grants no licenses or other rights, expressly or by implication, es
-toppel or otherwise, to: (1) make, use, sell, offer for sale, or import modifications of the Software; (2) sell or offer to sell the Software; (3) combine the Software or modified versions of the Software with other items or to use any such combination; o
-r (4) any claims of any patents, patent applications, or other patent rights of Intel other than the Licensed Patent Claims.
-\par
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 4.\tab }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\ul\cf1\insrsid13119444 OWNERSHIP AND COPYRIGHTS}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 .}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0
-\cf1\insrsid13119444 The Software and any Third Party Software are non-exclusively licensed to you, not sold. Title to a
-ll copies of the Software remains respectively with Intel and its suppliers and title to Third Party Software remain with the Third Party Software owners. The Software is copyrighted and protected by the laws of the United States and other countries, and
-international treaty provisions. You may not remove any copyright notices from the Software.
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444
-\par 5.\tab }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\ul\cf1\insrsid13119444 EXCLUSION OF WARRANTIES}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 .}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
- THE SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS. YOU ASSUME AND BEAR ALL RISKS ASSOCIATED WITH THE DOWNLOAD, INSTALLATION AND
-USE OF THE SOFTWARE. INTEL AND ITS SUPPLIERS PROVIDE NO WARRANTY OF ANY KIND WITH RESPECT TO THE SOFTWARE, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF MERCHANTABILITY, NONINFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS, FITNESS FOR A PA
-R
-TICULAR PURPOSE, OR ANY WARRANTY OTHERWISE ARISING OUT OF ANY PROPOSAL, SPECIFICATION OR SAMPLE. Intel and its suppliers do not warrant or assume responsibility for the accuracy or completeness of any information, text, graphics, links or other items con
-t
-ained within the Software. Intel and its suppliers do not warrant that your use of the Software will be uninterrupted or error-free, nor that defects in the Software will be corrected. You may have additional consumer rights under local laws in your jur
-isdiction which this License Agreement cannot change.
-\par
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 6.\tab }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\ul\cf1\insrsid13119444 LIMITATION OF LIABILITY}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 .}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0
-\cf1\insrsid13119444 IN NO EVENT SHALL INTEL OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, LOST PROFITS, BUSINESS INTERRUPTION, OR LOST INFORMATION) ARIS
-ING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE, EVEN IF INTEL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME JURISDICTIONS PROHIBIT EXCLUSION OR LIMITATION OF LIABILITY FOR IMPLIED WARRANTIES OR CONSEQUENTIAL OR INCIDENTAL DAMAGES, SO T
-HE ABOVE LIMITATION MAY NOT APPLY TO YOU.
-\par
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 7.\tab }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\ul\cf1\insrsid13119444 UNAUTHORIZED USES}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
-. THE SOFTWARE IS NOT DESIGNED, INTENDED, OR AUTHORIZED FOR USE IN ANY MEDICAL, LIFE SAVING OR LIFE SUSTAINING SYSTEMS, OR FOR ANY OTHER APPLICATION IN WHICH THE FAILURE OF THE SOFTWARE COULD
-CREATE A SITUATION WHERE PERSONAL INJURY OR DEATH MAY OCCUR. Should You purchase or use the Software for any such unintended or unauthorized use, You shall indemnify and hold Intel and its officers, subsidiaries and affiliates harmless against all claims,
-
-costs, damages, and expenses, and reasonable attorney fees arising out of, directly or indirectly, any claim of product liability, personal injury or death associated with such unintended or unauthorized use, even if such claim alleges that Intel was negl
-igent regarding the design or manufacture of the part.
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444
-\par 8.\tab CONSENT. You agree that Intel, its subsidiaries or suppliers may collect and use technical and related information, including but not limited to the software product serial number, technical inform
-ation about Your computer, system and application software, and peripherals, that is gathered periodically to facilitate the provision of software updates, product support and other services to You (if any) related to the Materials, and to verify complian
-ce with the terms of this Agreement. Intel may use this information, as long as it is in a form that does not personally identify You, to improve our products or to develop and provide services or technologies to You.
-\par
-\par 9.\tab }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\ul\cf1\insrsid13119444 COMMUNICATIONS}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 . }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444 You agree that an
-y material, information or other communication, including all data, images, sounds, text, and other things embodied therein, you transmit or post to an Intel website or provide to Intel under this Agreement will be considered non-confidential ("}{
-\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \ul\cf1\insrsid13119444 Communications}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
-"). Intel will have no confidentiality obligations with respect to the Communications. You agree that Intel and its designees will be free to copy, modify, create derivative works, publicly display, disclose, distribute, license and sublicense through
-multiple tiers of distribution and licensees, incorporate and otherwise use the Communications, including derivative works thereto, for any and all commercial or non-commercial purposes.
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444
-\par 10.\tab }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\ul\cf1\insrsid13119444 TERMINATION OF THIS LICENSE}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 . }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
-This Agreement becomes effective on the date you accept this Agreement and will continue until terminated as provided for in this Agreement. Intel may terminate this license at any time if you are in breach of any of its terms and conditions. Upon termina
-tion, you will immediately return to Intel or destroy the Software and all copies thereof. Sections 4, 5, 6, 7, 8, 9 (last 2 sentences), 10, 12, and 13 will survive expiration or termination of this License Agreement.
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444
-\par 11.\tab }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\ul\cf1\insrsid13119444 APPLICABLE LAWS.}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444 All disputes aris
-ing out of or related to this License Agreement, whether based on contract, tort or any other legal or equitable theory, will in all respects be governed, and construed and interpreted under, the laws of the United States of America and the State of Delaw
-a
-re, excluding its principles of conflict of laws . The parties agree that the United Nations Convention on Contracts for the International Sale of Goods (1980) is specifically excluded from and will not apply to this License Agreement. You may not export
- the Software in violation of applicable export laws and regulations. Intel is not obligated under any other agreements unless they are in writing and signed by an authorized representative of Intel.
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444
-\par 12.\tab }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\ul\cf1\insrsid13119444 U.S. GOVERNMENT RESTRICTED RIGHTS}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 .}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444 The technical dat
-a and computer software covered by this license is a \'93Commercial Item,\'94 as such term is defined by the FAR 2.101 (48 C.F.R. 2.101) and is \'93commercial computer software\'94 and \'93commercial computer software documentation\'94
- as specified under FAR 12.212 (48 C.F.
-R. 12.212) or DFARS 227.7202 (48 C.F.R. 227.7202), as applicable. This commercial computer software and related documentation is provided to end users for use by and on behalf of the U.S. Government, with only those rights as are granted to all other end
-u
-sers pursuant to the terms and conditions herein. Use for or on behalf of the U.S. Government is permitted only if the party acquiring or using this software is properly authorized by an appropriate U.S. Government official. This use by or for the U.S. Go
-v
-ernment clause is in lieu of, and supersedes, any other FAR, DFARS, or other provision that addresses Government rights in the computer software or documentation covered by this license. All copyright licenses granted to the U.S. Government are coextensi
-ve with the technical data and computer software licenses granted herein. The U.S. Government shall only have the right to reproduce, distribute, perform, display, and prepare derivative works as needed to implement those rights.
-\par
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 13.\tab }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\ul\cf1\insrsid13119444 Other General Information}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \b\cf1\insrsid13119444 :
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
-Intel is not responsible for any errors which may appear in the documentation or the Software, nor does Intel make a commitment to update the information or software contained therein. Intel reserves the right to make changes to the documentation or t
-he Software at any time, without notice.
-\par
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \ul\cf1\insrsid13119444 English Language}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
-. The English language version of this License Agreement shall be the only legally binding version and in the event of a conflict, inconsistency or difference of interpretation between the English
-language version and any other translation, the English language version shall prevail over such other translation. Any translation of this License Agreement is provided for convenience only and shall not be used in the interpretation or construction of
-this License Agreement and shall not be binding on the parties.
-\par
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \ul\cf1\insrsid13119444 Export Laws}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
-. You acknowledge that the Software and all related technical information or materials are subject to export controls under the laws and regulations of the United States and any ot
-her applicable governments. You agree to comply with these laws and regulations governing export, re-export, import, transfer, distribution, and use of the Software, technical information and materials. In particular, but without limitation, the Software,
-
-technical information and materials may not be exported or re-exported (a) into any U.S. embargoed countries or (b) to any person or entity listed on a denial order published by the U.S. government or any other applicable governments. By using the Softwar
-e
-, technical information and materials, you represent and warrant that you are not located in any such country or on any such list. You also agree that you will not use the Software, technical information and materials for any purposes prohibited by the U.
-S. government or other applicable government\rquote
-s law, including, without limitation, the development, design, manufacture or production of nuclear, missile, chemical or biological weapons. You confirm that the Software, technical information and materials wil
-l not be re-exported or sold to a third party who is known or suspected to be involved in activities including, without limitation, the development, design, manufacture, or production of nuclear, missile, chemical or biological weapons. You agree that any
-
-Software, technical information or materials subject to control under defense laws and regulations (e.g., the International Traffic in Arms Regulations (ITAR) must not be transferred to non-U.S. persons, whether located in the U.S. or abroad, without a go
-vernment license. You will indemnify Intel against any loss related to your failure to conform to these requirements.
-\par
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \ul\cf1\insrsid13119444 Entire Agreement}{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
-. This License Agreement contains the complete and exclusive agreement and understanding between the parties concerning th
-e subject matter of this License Agreement, and supersede all prior and contemporaneous proposals, agreements, understandings, negotiations, representations, warranties, conditions, and communications, oral or written, between the parties relating to the
-s
-ame subject matter. Each party acknowledges and agrees that in entering into this License Agreement it has not relied on, and will not be entitled to rely on, any oral or written representations, warranties, conditions, understandings or communications b
-e
-tween the parties that are not expressly set forth in this License Agreement. The express provisions of this License Agreement control over any course of dealing or usage of the trade inconsistent with any of the provisions of this License Agreement. The
-p
-rovisions of this License Agreement will prevail notwithstanding any different, conflicting or additional provisions that may appear on any purchase order, acknowledgement, invoice or other writing issued by either party in connection with this License Ag
-reement. No modification or amendment to this License Agreement will be effective unless in writing and signed by authorized representatives of each party.
-\par
-\par }\pard \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\faauto\rin0\lin0\itap0\pararsid13119444 {\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444 Intel and the Intel logo are trademarks or registered trademarks of Intel Corporation or its subsid
-iaries in the United States and other countries. * Other names and brands may be claimed as the property of others.
-\par }\pard \ltrpar\qj \li0\ri0\widctlpar\wrapdefault\faauto\rin0\lin0\itap0\pararsid13119444 {\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \cf1\insrsid13119444
-\par }{\rtlch\fcs1 \af0\afs18 \ltrch\fcs0 \fs16\cf1\insrsid13119444
-\par }\pard \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid13119444 {\rtlch\fcs1 \af1 \ltrch\fcs0 \insrsid1000228\charrsid13119444
-\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a
-9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad
-5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6
-b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0
-0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6
-a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f
-c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512
-0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462
-a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865
-6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b
-4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b
-4757e8d3f729e245eb2b260a0238fd010000ffff0300504b03041400060008000000210096b5ade296060000501b0000160000007468656d652f7468656d652f
-7468656d65312e786d6cec594f6fdb3614bf0fd87720746f6327761a07758ad8b19b2d4d1bc46e871e698996d850a240d2497d1bdae38001c3ba618715d86d87
-615b8116d8a5fb34d93a6c1dd0afb0475292c5585e9236d88aad3e2412f9e3fbff1e1fa9abd7eec70c1d1221294fda5efd72cd4324f1794093b0eddd1ef62fad
-79482a9c0498f184b4bd2991deb58df7dfbb8ad755446282607d22d771db8b944ad79796a40fc3585ee62949606ecc458c15bc8a702910f808e8c66c69b9565b
-5d8a314d3c94e018c8de1a8fa94fd05093f43672e23d06af89927ac06762a049136785c10607758d9053d965021d62d6f6804fc08f86e4bef210c352c144dbab
-999fb7b4717509af678b985ab0b6b4ae6f7ed9ba6c4170b06c788a705430adf71bad2b5b057d03606a1ed7ebf5babd7a41cf00b0ef83a6569632cd467faddec9
-699640f6719e76b7d6ac355c7c89feca9cccad4ea7d36c65b258a206641f1b73f8b5da6a6373d9c11b90c537e7f08dce66b7bbeae00dc8e257e7f0fd2badd586
-8b37a088d1e4600ead1ddaef67d40bc898b3ed4af81ac0d76a197c86826828a24bb318f3442d8ab518dfe3a20f000d6458d104a9694ac6d88728eee2782428d6
-0cf03ac1a5193be4cbb921cd0b495fd054b5bd0f530c1931a3f7eaf9f7af9e3f45c70f9e1d3ff8e9f8e1c3e3073f5a42ceaa6d9c84e5552fbffdeccfc71fa33f
-9e7ef3f2d117d57859c6fffac327bffcfc793510d26726ce8b2f9ffcf6ecc98baf3efdfdbb4715f04d814765f890c644a29be408edf3181433567125272371be
-15c308d3f28acd249438c19a4b05fd9e8a1cf4cd296699771c393ac4b5e01d01e5a30a787d72cf1178108989a2159c77a2d801ee72ce3a5c545a6147f32a9979
-3849c26ae66252c6ed637c58c5bb8b13c7bfbd490a75330f4b47f16e441c31f7184e140e494214d273fc80900aedee52ead87597fa824b3e56e82e451d4c2b4d
-32a423279a668bb6690c7e9956e90cfe766cb37b077538abd27a8b1cba48c80acc2a841f12e698f13a9e281c57911ce298950d7e03aba84ac8c154f8655c4f2a
-f074481847bd804859b5e696007d4b4edfc150b12addbecba6b18b148a1e54d1bc81392f23b7f84137c2715a851dd0242a633f900710a218ed715505dfe56e86
-e877f0034e16bafb0e258ebb4faf06b769e888340b103d3311da9750aa9d0a1cd3e4efca31a3508f6d0c5c5c398602f8e2ebc71591f5b616e24dd893aa3261fb
-44f95d843b5974bb5c04f4edafb95b7892ec1108f3f98de75dc97d5772bdff7cc95d94cf672db4b3da0a6557f70db629362d72bcb0431e53c6066acac80d699a
-6409fb44d08741bdce9c0e4971624a2378cceaba830b05366b90e0ea23aaa241845368b0eb9e2612ca8c742851ca251ceccc70256d8d87265dd96361531f186c
-3d9058edf2c00eafe8e1fc5c509031bb4d680e9f39a3154de0accc56ae644441edd76156d7429d995bdd88664a9dc3ad50197c38af1a0c16d684060441db0256
-5e85f3b9660d0713cc48a0ed6ef7dedc2dc60b17e92219e180643ed27acffba86e9c94c78ab90980d8a9f0913ee49d62b512b79626fb06dccee2a432bbc60276
-b9f7dec44b7904cfbca4f3f6443ab2a49c9c2c41476dafd55c6e7ac8c769db1bc399161ee314bc2e75cf8759081743be1236ec4f4d6693e5336fb672c5dc24a8
-c33585b5fb9cc24e1d4885545b58463634cc5416022cd19cacfccb4d30eb45296023fd35a458598360f8d7a4003bbaae25e331f155d9d9a5116d3bfb9a95523e
-51440ca2e0088dd844ec6370bf0e55d027a012ae264c45d02f708fa6ad6da6dce29c255df9f6cae0ec38666984b372ab5334cf640b37795cc860de4ae2816e95
-b21be5ceaf8a49f90b52a51cc6ff3355f47e0237052b81f6800fd7b802239daf6d8f0b1571a8426944fdbe80c6c1d40e8816b88b8569082ab84c36ff0539d4ff
-6dce591a26ade1c0a7f669880485fd484582903d284b26fa4e2156cff62e4b9265844c4495c495a9157b440e091bea1ab8aaf7760f4510eaa69a6465c0e04ec6
-9ffb9e65d028d44d4e39df9c1a52ecbd3607fee9cec7263328e5d661d3d0e4f62f44acd855ed7ab33cdf7bcb8ae889599bd5c8b3029895b6825696f6af29c239
-b75a5bb1e6345e6ee6c28117e73586c1a2214ae1be07e93fb0ff51e133fb65426fa843be0fb515c187064d0cc206a2fa926d3c902e907670048d931db4c1a449
-59d366ad93b65abe595f70a75bf03d616c2dd959fc7d4e6317cd99cbcec9c58b34766661c7d6766ca1a9c1b327531486c6f941c638c67cd22a7f75e2a37be0e8
-2db8df9f30254d30c1372581a1f51c983c80e4b71ccdd28dbf000000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468
-656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4
-350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d2624
-52282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe5141
-73d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c020000130000000000000000
-0000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000
-000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c0000000000000000000000000019
-0200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d001400060008000000210096b5ade296060000501b00001600000000
-000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b01000027
-00000000000000000000000000a00900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d0100009b0a00000000}
-{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d
-617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169
-6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363
-656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e}
-{\*\latentstyles\lsdstimax371\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 Normal;\lsdqformat1 \lsdpriority0 heading 1;
-\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority0 heading 4;
-\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority0 heading 7;
-\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority0 heading 9;\lsdpriority0 toc 1;\lsdpriority0 toc 2;\lsdpriority0 toc 3;\lsdpriority0 toc 4;\lsdpriority0 toc 5;
-\lsdpriority0 toc 6;\lsdpriority0 toc 7;\lsdpriority0 toc 8;\lsdpriority0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;
-\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdqformat1 \lsdpriority0 Title;\lsdpriority1 Default Paragraph Font;\lsdqformat1 \lsdpriority0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation;
-\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdqformat1 \lsdpriority0 Strong;\lsdqformat1 \lsdpriority0 Emphasis;\lsdpriority0 Table Grid;
-\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;
-\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;
-\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;
-\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;
-\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;
-\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;
-\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;
-\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;
-\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;
-\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;
-\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;
-\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;
-\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;
-\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;
-\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;
-\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5;
-\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5;
-\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5;
-\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6;
-\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6;
-\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6;
-\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis;
-\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography;
-\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4;
-\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4;
-\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1;
-\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1;
-\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2;
-\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2;
-\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3;
-\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4;
-\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4;
-\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5;
-\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5;
-\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6;
-\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6;
-\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark;
-\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1;
-\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1;
-\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2;
-\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3;
-\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3;
-\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4;
-\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4;
-\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5;
-\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5;
-\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6;
-\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;}}{\*\datastore 010500000200000018000000
-4d73786d6c322e534158584d4c5265616465722e362e30000000000000000000000e0000
-d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000000200000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-fffffffffffffffffdffffff04000000feffffff05000000fefffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffff010000000c6ad98892f1d411a65f0040963251e5000000000000000000000000d034
-c2c1cc9fd1010300000080020000000000004d0073006f004400610074006100530074006f0072006500000000000000000000000000000000000000000000000000000000000000000000000000000000001a000101ffffffffffffffff020000000000000000000000000000000000000000000000d034c2c1cc9fd101
-d034c2c1cc9fd1010000000000000000000000004b00cd003400ce00d200d000dd005800ce00450057005200c100c000c3004300430050004800d9005a0041003d003d000000000000000000000000000000000032000101ffffffffffffffff030000000000000000000000000000000000000000000000d034c2c1cc9f
-d101d034c2c1cc9fd1010000000000000000000000004900740065006d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000201ffffffff04000000ffffffff000000000000000000000000000000000000000000000000
-00000000000000000000000000000000d800000000000000010000000200000003000000feffffff0500000006000000070000000800000009000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3c623a536f75726365732053656c65637465645374796c653d225c4150412e58534c22205374796c654e616d653d224150412220786d6c6e733a623d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f7267
-2f6f6666696365446f63756d656e742f323030362f6269626c696f6772617068792220786d6c6e733d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879223e3c2f623a536f75726365733e00000000
-0000000000000000000000000000000000000000000000000000000000000000000000003c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d226e6f223f3e0d0a3c64733a6461746173746f72654974656d2064733a6974656d49443d227b43424145
-443732412d353730462d343542382d393138362d3038433230384631463936347d2220786d6c6e733a64733d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f637573746f6d586d6c223e3c64733a736368656d61526566733e3c
-64733a736368656d615265662064733a7572693d22687474703a2f2f736368656d61732e6f70656e500072006f007000650072007400690065007300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000200ffffffffffffffffffffffff000000000000
-0000000000000000000000000000000000000000000000000000000000000400000055010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000
-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000
-000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff
-000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879222f3e3c2f64733a736368656d61526566733e3c2f64733a6461746173746f
-72654974656d3e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file
diff --git a/tracker-rs/redist/intel_rs_sdk_runtime_websetup_10.0.26.0396.exe b/tracker-rs/redist/intel_rs_sdk_runtime_websetup_10.0.26.0396.exe
deleted file mode 100644
index 90000041..00000000
--- a/tracker-rs/redist/intel_rs_sdk_runtime_websetup_10.0.26.0396.exe
+++ /dev/null
Binary files differ
diff --git a/tracker-rs/rs_impl/CMakeLists.txt b/tracker-rs/rs_impl/CMakeLists.txt
index d4bf0925..97c6fd99 100644
--- a/tracker-rs/rs_impl/CMakeLists.txt
+++ b/tracker-rs/rs_impl/CMakeLists.txt
@@ -5,7 +5,7 @@ if(WIN32)
target_link_libraries(opentrack-tracker-rs-impl advapi32)
# for SDK headers
- if(CMAKE_COMPILER_IS_GNUCC)
+ if(CMAKE_COMPILER_IS_GNUCXX)
add_definitions(-fpermissive -Wno-error -w
#-Wno-missing-field-initializers -Wno-switch -Wno-sign-compare
#-Wno-unknown-pragmas -Wno-attributes
diff --git a/tracker-rs/rs_impl/attic/build.bat b/tracker-rs/rs_impl/attic/build.bat
index 3d5e8bc0..9fa3f859 100644
--- a/tracker-rs/rs_impl/attic/build.bat
+++ b/tracker-rs/rs_impl/attic/build.bat
@@ -1,29 +1,29 @@
-@echo off
-
-setlocal
-
-IF DEFINED VS150COMNTOOLS (
- if exist "%VS150COMNTOOLS%\..\..\VC\vcvarsall.bat" (
- set vs_dir="%VS150COMNTOOLS%\..\..\VC"
- set vs_64=amd64
- ) else (
- rem it installed vcvarsall.bat here for me for some reason -sh 20160827
- set vs_dir="%VS150COMNTOOLS%\..\ide\vc"
- set vs_64=amd64
- )) else (
- set vs_dir="%VS140COMNTOOLS%\..\..\VC"
- set vs_64=amd64
- )
-
-cd /d "%~dp0"
-if %errorlevel% neq 0 exit /b 1
-
-set rs_objs=ftnoir_tracker_rs_impl.obj libpxc.obj
-
-set rs_subdir=ia32&set vs_var=x86
-call ".\build_common.bat" || exit /b 1
-
-set rs_subdir=amd64&set vs_var=%vs_64%
-call ".\build_common.bat" || exit /b 1
-
-exit /b 0
+@echo off
+
+setlocal
+
+IF DEFINED VS150COMNTOOLS (
+ if exist "%VS150COMNTOOLS%\..\..\VC\vcvarsall.bat" (
+ set vs_dir="%VS150COMNTOOLS%\..\..\VC"
+ set vs_64=amd64
+ ) else (
+ rem it installed vcvarsall.bat here for me for some reason -sh 20160827
+ set vs_dir="%VS150COMNTOOLS%\..\ide\vc"
+ set vs_64=amd64
+ )) else (
+ set vs_dir="%VS140COMNTOOLS%\..\..\VC"
+ set vs_64=amd64
+ )
+
+cd /d "%~dp0"
+if %errorlevel% neq 0 exit /b 1
+
+set rs_objs=ftnoir_tracker_rs_impl.obj libpxc.obj
+
+set rs_subdir=ia32&set vs_var=x86
+call ".\build_common.bat" || exit /b 1
+
+set rs_subdir=amd64&set vs_var=%vs_64%
+call ".\build_common.bat" || exit /b 1
+
+exit /b 0
diff --git a/tracker-rs/rs_impl/attic/build_common.bat b/tracker-rs/rs_impl/attic/build_common.bat
index c37e0ae8..87453cba 100644
--- a/tracker-rs/rs_impl/attic/build_common.bat
+++ b/tracker-rs/rs_impl/attic/build_common.bat
@@ -1,21 +1,21 @@
-@echo off
-
-setlocal
-
-cd /d .\bin\%rs_subdir% || exit 1
-
-echo --- %rs_subdir%
-
-set rice_ld=/OPT:REF /OPT:ICF=10 /DEBUG /DYNAMICBASE /NXCOMPAT /LTCG
-set rice_lib=
-set rice_cc=/Ox /arch:SSE2 /EHscr /fp:fast /GS- /GF /GR- /Gy /MT /Y- /Zi /W1 /GL /Zi
-set libs=advapi32.lib
-
-if not exist %vs_dir%\vcvarsall.bat exit /b 1
-call %vs_dir%\vcvarsall %vs_var% || exit /b 1
-if ["%libpath%"] == [""] exit /b 1
-
-cl /c /nologo -DEXPORT_RS_IMPL -DUNICODE -D_UNICODE -MT %rice_cc% /I "%RSSDK_DIR%\opensource\include" ..\..\ftnoir_tracker_rs_impl.cpp "%RSSDK_DIR%\opensource\src\libpxc\libpxc.cpp" || exit /b 1
-lib /nologo %rice_lib% %rs_objs% %libs% /OUT:rs-impl.lib || exit /b 1
-
-exit /b 0
+@echo off
+
+setlocal
+
+cd /d .\bin\%rs_subdir% || exit 1
+
+echo --- %rs_subdir%
+
+set rice_ld=/OPT:REF /OPT:ICF=10 /DEBUG /DYNAMICBASE /NXCOMPAT /LTCG
+set rice_lib=
+set rice_cc=/Ox /arch:SSE2 /EHscr /fp:fast /GS- /GF /GR- /Gy /MT /Y- /Zi /W1 /GL /Zi
+set libs=advapi32.lib
+
+if not exist %vs_dir%\vcvarsall.bat exit /b 1
+call %vs_dir%\vcvarsall %vs_var% || exit /b 1
+if ["%libpath%"] == [""] exit /b 1
+
+cl /c /nologo -DEXPORT_RS_IMPL -DUNICODE -D_UNICODE -MT %rice_cc% /I "%RSSDK_DIR%\opensource\include" ..\..\ftnoir_tracker_rs_impl.cpp "%RSSDK_DIR%\opensource\src\libpxc\libpxc.cpp" || exit /b 1
+lib /nologo %rice_lib% %rs_objs% %libs% /OUT:rs-impl.lib || exit /b 1
+
+exit /b 0
diff --git a/tracker-rs/rs_impl/ftnoir_tracker_rs_impl.cpp b/tracker-rs/rs_impl/ftnoir_tracker_rs_impl.cpp
index f159d60b..f54531a6 100644
--- a/tracker-rs/rs_impl/ftnoir_tracker_rs_impl.cpp
+++ b/tracker-rs/rs_impl/ftnoir_tracker_rs_impl.cpp
@@ -5,13 +5,25 @@
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "ftnoir_tracker_rs_impl.h"
+
+#ifdef _WIN32
+# include <windows.h>
+#endif
+#include <cstdlib>
+
#include <pxcsensemanager.h>
+#if 0
+#include <RealSense/Face/FaceModule.h>
+#include <RealSense/Face/FaceConfiguration.h>
+using PXCFaceData = Intel::RealSense::Face::FaceData;
+using PXCFaceConfiguration = Intel::RealSense::Face::FaceConfiguration;
+#else
#include <pxcfacemodule.h>
#include <pxcfaceconfiguration.h>
-#include <windows.h>
+#endif
-const size_t kPreviewStreamWidth = 640;
-const size_t kPreviewStreamHeight = 480;
+constexpr size_t kPreviewStreamWidth = 640;
+constexpr size_t kPreviewStreamHeight = 480;
PXCSenseManager* g_senseManager = NULL;
PXCFaceData* g_faceData = NULL;
diff --git a/tracker-rs/rs_impl/ftnoir_tracker_rs_impl.h b/tracker-rs/rs_impl/ftnoir_tracker_rs_impl.h
index a4c03ce8..c71f30fa 100644
--- a/tracker-rs/rs_impl/ftnoir_tracker_rs_impl.h
+++ b/tracker-rs/rs_impl/ftnoir_tracker_rs_impl.h
@@ -6,15 +6,8 @@
*/
#pragma once
-#ifdef EXPORT_RS_IMPL
-#define RSTRACKERIMPL_VISIBILITY __declspec( dllexport )
-#else
-#define RSTRACKERIMPL_VISIBILITY
-#endif
-extern "C" {
- RSTRACKERIMPL_VISIBILITY int rs_tracker_impl_start();
- RSTRACKERIMPL_VISIBILITY int rs_tracker_impl_update_pose(double *pose);
- RSTRACKERIMPL_VISIBILITY int rs_tracker_impl_get_preview(void* data, int width, int height);
- RSTRACKERIMPL_VISIBILITY int rs_tracker_impl_end();
-}
+int rs_tracker_impl_start();
+int rs_tracker_impl_update_pose(double *pose);
+int rs_tracker_impl_get_preview(void* data, int width, int height);
+int rs_tracker_impl_end();
diff --git a/tracker-s2bot/ftnoir_tracker_s2bot.cpp b/tracker-s2bot/ftnoir_tracker_s2bot.cpp
index 44ae6132..98a299ca 100644
--- a/tracker-s2bot/ftnoir_tracker_s2bot.cpp
+++ b/tracker-s2bot/ftnoir_tracker_s2bot.cpp
@@ -27,13 +27,22 @@ static constexpr int add_cbx[] =
-180,
};
+#ifdef __GNUG__
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
void tracker_s2bot::run() {
- if (s.freq == 0) s.freq = 10;
- timer.setInterval(1000.0/s.freq);
+ int freq = s.freq;
+ if (freq <= 0)
+ freq = 10;
+ timer.setInterval((int)(1000./freq));
timer.setSingleShot(false);
- connect(&timer, &QTimer::timeout, [this]() {
- auto reply = m_nam->get(QNetworkRequest(QUrl("http://localhost:17317/poll")));
- connect(reply, &QNetworkReply::finished, [this, reply]() {
+ connect(&timer, &QTimer::timeout, [this] {
+ QNetworkRequest req{QUrl("http://localhost:17317/poll")};
+ req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
+ auto* reply = m_nam->get(req);
+
+ connect(reply, &QNetworkReply::finished, [this, reply] {
if (reply->error() == QNetworkReply::NoError) {
//qDebug() << "Request submitted OK";
}
@@ -48,15 +57,15 @@ void tracker_s2bot::run() {
int order[] =
{
- clamp(s.idx_x, 0, 3),
- clamp(s.idx_y, 0, 3),
- clamp(s.idx_z, 0, 3),
+ std::clamp(*s.idx_x, 0, 3),
+ std::clamp(*s.idx_y, 0, 3),
+ std::clamp(*s.idx_z, 0, 3),
};
int add_indices[] = { s.add_yaw, s.add_pitch, s.add_roll, };
double orient[4] {};
- for (auto line : slist)
+ for (auto const& line : slist)
{
QStringList keyval = line.split(' ');
if (keyval.count() < 2) continue;
diff --git a/tracker-s2bot/ftnoir_tracker_s2bot.h b/tracker-s2bot/ftnoir_tracker_s2bot.h
index eddf58f4..b05a226c 100644
--- a/tracker-s2bot/ftnoir_tracker_s2bot.h
+++ b/tracker-s2bot/ftnoir_tracker_s2bot.h
@@ -65,8 +65,9 @@ private slots:
class meta_s2bot : public Metadata
{
-public:
- QString name() { return otr_tr("S2Bot receiver"); }
+ Q_OBJECT
+
+ QString name() { return tr("S2Bot receiver"); }
QIcon icon() { return QIcon(":/s2bot.png"); }
};
diff --git a/tracker-s2bot/lang/de_DE.ts b/tracker-s2bot/lang/de_DE.ts
new file mode 100644
index 00000000..4071d117
--- /dev/null
+++ b/tracker-s2bot/lang/de_DE.ts
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>UI_s2bot_dialog</name>
+ <message>
+ <source>Tracker settings</source>
+ <translation>Tracker-Einstellungen</translation>
+ </message>
+ <message>
+ <source>Update frequency (Hz)</source>
+ <translation>Aktualisierungsfrequenz (Hz)</translation>
+ </message>
+ <message>
+ <source>Axis order</source>
+ <translation>Achsen-Reihenfolge</translation>
+ </message>
+ <message>
+ <source>output yaw</source>
+ <translation>Gieren-Ausgabe</translation>
+ </message>
+ <message>
+ <source>input yaw</source>
+ <translation>Gieren-Eingabe</translation>
+ </message>
+ <message>
+ <source>input pitch</source>
+ <translation>Nicken-Eingabe</translation>
+ </message>
+ <message>
+ <source>input roll</source>
+ <translation>Rollen-Eingabe</translation>
+ </message>
+ <message>
+ <source>input bearing</source>
+ <translation>Peilung-Eingabe</translation>
+ </message>
+ <message>
+ <source>output pitch</source>
+ <translation>Nicken-Ausgabe</translation>
+ </message>
+ <message>
+ <source>output roll</source>
+ <translation>Rollen-Ausgabe</translation>
+ </message>
+ <message>
+ <source>Add to axis</source>
+ <translation>Zur Achse hinzufügen</translation>
+ </message>
+ <message>
+ <source>yaw</source>
+ <translation>Gieren</translation>
+ </message>
+ <message>
+ <source>0</source>
+ <translation>0</translation>
+ </message>
+ <message>
+ <source>+90</source>
+ <translation>+90</translation>
+ </message>
+ <message>
+ <source>-90</source>
+ <translation>-90</translation>
+ </message>
+ <message>
+ <source>+180</source>
+ <translation>+180</translation>
+ </message>
+ <message>
+ <source>-180</source>
+ <translation>-180</translation>
+ </message>
+ <message>
+ <source>pitch</source>
+ <translation>Nicken</translation>
+ </message>
+ <message>
+ <source>roll</source>
+ <translation>Rollen</translation>
+ </message>
+</context>
+<context>
+ <name>meta_s2bot</name>
+ <message>
+ <source>S2Bot receiver</source>
+ <translation>S2Bot-Empfänger</translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-s2bot/lang/nl_NL.ts b/tracker-s2bot/lang/nl_NL.ts
index 12e39d07..8ea33f46 100644
--- a/tracker-s2bot/lang/nl_NL.ts
+++ b/tracker-s2bot/lang/nl_NL.ts
@@ -80,4 +80,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>meta_s2bot</name>
+ <message>
+ <source>S2Bot receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-s2bot/lang/ru_RU.ts b/tracker-s2bot/lang/ru_RU.ts
index 22b8000e..2d5c479b 100644
--- a/tracker-s2bot/lang/ru_RU.ts
+++ b/tracker-s2bot/lang/ru_RU.ts
@@ -80,4 +80,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>meta_s2bot</name>
+ <message>
+ <source>S2Bot receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-s2bot/lang/stub.ts b/tracker-s2bot/lang/stub.ts
index 692400cf..babca884 100644
--- a/tracker-s2bot/lang/stub.ts
+++ b/tracker-s2bot/lang/stub.ts
@@ -80,4 +80,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>meta_s2bot</name>
+ <message>
+ <source>S2Bot receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-s2bot/lang/zh_CN.ts b/tracker-s2bot/lang/zh_CN.ts
index 692400cf..f9cc2a4b 100644
--- a/tracker-s2bot/lang/zh_CN.ts
+++ b/tracker-s2bot/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>UI_s2bot_dialog</name>
<message>
@@ -9,27 +9,27 @@
</message>
<message>
<source>Update frequency (Hz)</source>
- <translation type="unfinished"></translation>
+ <translation>刷新率(Hz)</translation>
</message>
<message>
<source>Axis order</source>
- <translation type="unfinished"></translation>
+ <translation>è¾“å‡ºé‡æ˜ å°„</translation>
</message>
<message>
<source>output yaw</source>
- <translation type="unfinished"></translation>
+ <translation>输出的航å‘</translation>
</message>
<message>
<source>input yaw</source>
- <translation type="unfinished"></translation>
+ <translation type="unfinished">输入航å‘</translation>
</message>
<message>
<source>input pitch</source>
- <translation type="unfinished"></translation>
+ <translation type="unfinished">输入俯仰</translation>
</message>
<message>
<source>input roll</source>
- <translation type="unfinished"></translation>
+ <translation type="unfinished">输入滚转</translation>
</message>
<message>
<source>input bearing</source>
@@ -37,46 +37,53 @@
</message>
<message>
<source>output pitch</source>
- <translation type="unfinished"></translation>
+ <translation>输出的俯仰</translation>
</message>
<message>
<source>output roll</source>
- <translation type="unfinished"></translation>
+ <translation>输出的滚转</translation>
</message>
<message>
<source>Add to axis</source>
- <translation type="unfinished"></translation>
+ <translation>添加到轴</translation>
</message>
<message>
<source>yaw</source>
- <translation type="unfinished"></translation>
+ <translation>航å‘</translation>
</message>
<message>
<source>0</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>+90</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>-90</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>+180</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>-180</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<source>pitch</source>
- <translation type="unfinished"></translation>
+ <translation>俯仰</translation>
</message>
<message>
<source>roll</source>
+ <translation>滚转</translation>
+ </message>
+</context>
+<context>
+ <name>meta_s2bot</name>
+ <message>
+ <source>S2Bot receiver</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-steamvr/CMakeLists.txt b/tracker-steamvr/CMakeLists.txt
index 512a49ff..eb8b20b9 100644
--- a/tracker-steamvr/CMakeLists.txt
+++ b/tracker-steamvr/CMakeLists.txt
@@ -2,10 +2,13 @@ set(steamvr-dir "")
set(steamvr-dll "")
set(steamvr-lib "")
-if(LINUX AND opentrack-64bit)
- set(steamvr-dir "linux64")
+if(LINUX)
+ if (opentrack-64bit)
+ set(steamvr-dir "linux64")
+ else()
+ set(steamvr-dir "linux32")
+ endif()
set(steamvr-dll "libopenvr_api.so")
- set(steamvr-lib "${steamvr-dll}")
endif()
if(WIN32)
@@ -18,19 +21,26 @@ if(WIN32)
set(steamvr-lib "openvr_api.lib")
endif()
-if(APPLE AND NOT opentrack-64bit)
- set(steamvr-dir "osx32")
+if(APPLE)
+ # expect user compiled it as a non-framework version
+ if(opentrack-64bit)
+ set(steamvr-dir "osx64")
+ else()
+ set(steamvr-dir "osx32")
+ endif()
set(steamvr-dll "libopenvr_api.dylib")
- set(steamvr-lib "${steamvr-dll}")
endif()
-if(steamvr-dll)
+if(steamvr-dll AND opentrack-intel)
+ if(steamvr-lib STREQUAL "")
+ set(steamvr-lib "${steamvr-dll}")
+ endif()
SET(SDK_VALVE_STEAMVR "" CACHE PATH "Valve's SteamVR")
if(SDK_VALVE_STEAMVR)
otr_module(tracker-steamvr)
- install(FILES "${SDK_VALVE_STEAMVR}/bin/${steamvr-dir}/${steamvr-dll}" DESTINATION "${opentrack-hier-pfx}")
+ install(FILES "${SDK_VALVE_STEAMVR}/bin/${steamvr-dir}/${steamvr-dll}" DESTINATION "${opentrack-libexec}")
- target_include_directories(opentrack-tracker-steamvr SYSTEM PUBLIC "${SDK_VALVE_STEAMVR}/headers")
+ target_include_directories(opentrack-tracker-steamvr SYSTEM PRIVATE "${SDK_VALVE_STEAMVR}/headers")
target_link_libraries(opentrack-tracker-steamvr "${SDK_VALVE_STEAMVR}/lib/${steamvr-dir}/${steamvr-lib}")
endif()
endif()
diff --git a/tracker-steamvr/lang/nl_NL.ts b/tracker-steamvr/lang/nl_NL.ts
index b8def443..37c3130d 100644
--- a/tracker-steamvr/lang/nl_NL.ts
+++ b/tracker-steamvr/lang/nl_NL.ts
@@ -23,4 +23,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>steamvr_metadata</name>
+ <message>
+ <source>Valve SteamVR</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-steamvr/lang/ru_RU.ts b/tracker-steamvr/lang/ru_RU.ts
index 019b3616..380518d0 100644
--- a/tracker-steamvr/lang/ru_RU.ts
+++ b/tracker-steamvr/lang/ru_RU.ts
@@ -23,4 +23,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>steamvr_metadata</name>
+ <message>
+ <source>Valve SteamVR</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-steamvr/lang/stub.ts b/tracker-steamvr/lang/stub.ts
index 9c2782ac..7b68034b 100644
--- a/tracker-steamvr/lang/stub.ts
+++ b/tracker-steamvr/lang/stub.ts
@@ -23,4 +23,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>steamvr_metadata</name>
+ <message>
+ <source>Valve SteamVR</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-steamvr/lang/zh_CN.ts b/tracker-steamvr/lang/zh_CN.ts
index 9c2782ac..2c6f7230 100644
--- a/tracker-steamvr/lang/zh_CN.ts
+++ b/tracker-steamvr/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>dialog</name>
<message>
@@ -23,4 +23,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>steamvr_metadata</name>
+ <message>
+ <source>Valve SteamVR</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-steamvr/steamvr.cpp b/tracker-steamvr/steamvr.cpp
index 32cb6ed1..05b5ed35 100644
--- a/tracker-steamvr/steamvr.cpp
+++ b/tracker-steamvr/steamvr.cpp
@@ -20,10 +20,8 @@
#include "api/plugin-api.hpp"
-#include <cstdlib>
#include <cmath>
-#include <type_traits>
-#include <algorithm>
+#include <cstdlib>
#include <QMessageBox>
#include <QDebug>
@@ -31,17 +29,16 @@
QMutex device_list::mtx(QMutex::Recursive);
template<typename F>
-auto with_vr_lock(F&& fun) -> decltype(fun(vr_t(), error_t()))
+auto with_vr_lock(F&& fun) -> decltype(fun(vr_t(), vr_error_t()))
{
QMutexLocker l(&device_list::mtx);
- error_t e; vr_t v;
- std::tie(v, e) = device_list::vr_init();
+ auto [v, e] = device_list::vr_init();
return fun(v, e);
}
void device_list::fill_device_specs(QList<device_spec>& list)
{
- with_vr_lock([&](vr_t v, error_t)
+ with_vr_lock([&](vr_t v, vr_error_t)
{
list.clear();
@@ -60,13 +57,13 @@ void device_list::fill_device_specs(QList<device_spec>& list)
{
if (v->GetTrackedDeviceClass(k) == vr::ETrackedDeviceClass::TrackedDeviceClass_Invalid)
{
- qDebug() << "no device with index";
+ qDebug() << "steamvr: no device with index";
continue;
}
if (!device_states[k].bDeviceIsConnected)
{
- qDebug() << "device not connected but proceeding";
+ qDebug() << "steamvr: device not connected but proceeding";
continue;
}
@@ -92,12 +89,17 @@ void device_list::fill_device_specs(QList<device_spec>& list)
switch (v->GetTrackedDeviceClass(k))
{
- case vr::ETrackedDeviceClass::TrackedDeviceClass_HMD:
+ using enum vr::ETrackedDeviceClass;
+ case TrackedDeviceClass_HMD:
dev.type = "HMD"; break;
- case vr::ETrackedDeviceClass::TrackedDeviceClass_Controller:
+ case TrackedDeviceClass_Controller:
dev.type = "Controller"; break;
- case vr::ETrackedDeviceClass::TrackedDeviceClass_TrackingReference:
- dev.type = "Tracker"; break;
+ case TrackedDeviceClass_TrackingReference:
+ dev.type = "Tracking reference"; break;
+ case TrackedDeviceClass_DisplayRedirect:
+ dev.type = "Display redirect"; break;
+ case TrackedDeviceClass_GenericTracker:
+ dev.type = "Generic"; break;
default:
dev.type = "Unknown"; break;
}
@@ -126,10 +128,10 @@ void device_list::refresh_device_list()
device_list::maybe_pose device_list::get_pose(int k)
{
- if (k < 0 || !(k < max_devices))
+ if (!(unsigned(k) < max_devices))
return maybe_pose(false, pose_t{});
- return with_vr_lock([k](vr_t v, error_t)
+ return with_vr_lock([k](vr_t v, vr_error_t)
{
static pose_t poses[max_devices] {}; // vr_lock removes reentrancy
@@ -139,14 +141,14 @@ device_list::maybe_pose device_list::get_pose(int k)
const pose_t& pose = poses[k];
if (pose.bPoseIsValid && pose.bDeviceIsConnected)
- return maybe_pose(true, poses[k]);
+ return maybe_pose{ true, poses[k] };
else
- once_only(qDebug() << "steamvr:"
+ eval_once(qDebug() << "steamvr:"
<< "no valid pose from device" << k
<< "valid" << pose.bPoseIsValid
<< "connected" << pose.bDeviceIsConnected);
- return maybe_pose(false, pose_t{});
+ return maybe_pose{ false, {} };
});
}
@@ -158,40 +160,43 @@ tt device_list::vr_init()
tt device_list::vr_init_()
{
- error_t error = error_t::VRInitError_Unknown;
+ vr_error_t error = vr_error_t::VRInitError_Unknown;
vr_t v = vr::VR_Init(&error, vr::EVRApplicationType::VRApplication_Other);
if (v)
std::atexit(vr::VR_Shutdown);
else
- qDebug() << "steamvr: init failure" << error << device_list::strerror(error);
+ qDebug() << "steamvr: init failure" << error << device_list::error_string(error);
- return tt(v, error);
+ return { v, error };
}
-QString device_list::strerror(error_t err)
+QString device_list::error_string(vr_error_t err)
{
- const char* str(vr::VR_GetVRInitErrorAsSymbol(err));
- return QString(str ? str : "No description");
-}
+ const char* str = vr::VR_GetVRInitErrorAsSymbol(err);
+ const char* desc = vr::VR_GetVRInitErrorAsEnglishDescription(err);
-steamvr::steamvr() : device_index(-1)
-{
-}
+ if (!desc)
+ desc = "No description";
-steamvr::~steamvr()
-{
+ if (str)
+ return QStringLiteral("%1: %2").arg(str, desc);
+ else
+ return { "Unknown error" };
}
+steamvr::steamvr() = default;
+steamvr::~steamvr() = default;
+
module_status steamvr::start_tracker(QFrame*)
{
- return with_vr_lock([this](vr_t v, error_t e)
+ return with_vr_lock([this](vr_t v, vr_error_t e)
{
QString err;
if (!v)
{
- err = device_list::strerror(e);
+ err = device_list::error_string(e);
return error(err);
}
@@ -203,42 +208,47 @@ module_status steamvr::start_tracker(QFrame*)
if (sz == 0)
err = tr("No HMD connected");
- device_index = -1;
-
for (const device_spec& spec : specs)
{
if (serial == "" || serial == spec.to_string())
{
- device_index = int(spec.k);
+ device_index = spec.k;
break;
}
}
- if (device_index == -1 && err.isEmpty())
+ if (device_index == UINT_MAX && err.isEmpty())
err = tr("Can't find device with that serial");
if (err.isEmpty())
- return status_ok();
- else
- return error(err);
+ {
+ if (auto* c = vr::VRCompositor(); c != nullptr)
+ {
+ c->SetTrackingSpace(origin::TrackingUniverseSeated);
+ return status_ok();
+ }
+ else
+ return error("vr::VRCompositor == NULL");
+ }
+
+ return error(err);
});
}
void steamvr::data(double* data)
{
- if (device_index != -1)
+ if (device_index != UINT_MAX)
{
- pose_t pose; bool ok;
- std::tie(ok, pose) = device_list::get_pose(device_index);
+ auto [ok, pose] = device_list::get_pose(device_index);
if (ok)
{
constexpr int c = 10;
const auto& result = pose.mDeviceToAbsoluteTracking;
- data[TX] = -result.m[0][3] * c;
- data[TY] = result.m[1][3] * c;
- data[TZ] = result.m[2][3] * c;
+ data[TX] = (double)(-result.m[0][3] * c);
+ data[TY] = (double)(result.m[1][3] * c);
+ data[TZ] = (double)(result.m[2][3] * c);
matrix_to_euler(data[Yaw], data[Pitch], data[Roll], result);
@@ -250,18 +260,25 @@ void steamvr::data(double* data)
bool steamvr::center()
{
- return with_vr_lock([&](vr_t v, error_t)
+ return with_vr_lock([&](vr_t v, vr_error_t)
{
if (v)
{
if (v->GetTrackedDeviceClass(device_index) == vr::ETrackedDeviceClass::TrackedDeviceClass_HMD)
{
- // Reset yaw and position
- v->ResetSeatedZeroPose();
-
- // Use chaperone universe real world up instead of opentrack's initial pose centering
- // Note: Controllers will be centered based on initial headset position.
- return true;
+ auto* c = vr::VRChaperone();
+ if (!c)
+ {
+ eval_once(qDebug() << "steamvr: vr::VRChaperone == NULL");
+ return false;
+ }
+ else
+ {
+ c->ResetZeroPose(origin::TrackingUniverseSeated);
+ // Use chaperone universe real world up instead of opentrack's initial pose centering
+ // Note: Controllers will be centered based on initial headset position.
+ return true;
+ }
}
else
// with controllers, resetting the seated pose does nothing
@@ -273,15 +290,11 @@ bool steamvr::center()
void steamvr::matrix_to_euler(double& yaw, double& pitch, double& roll, const vr::HmdMatrix34_t& result)
{
- using std::atan2;
- using std::sqrt;
- using std::asin;
-
using d = double;
- yaw = atan2(d(result.m[2][0]), d(result.m[0][0]));
- pitch = atan2(-d(result.m[1][2]), d(result.m[1][1]));
- roll = asin(d(result.m[1][0]));
+ yaw = std::atan2(d(result.m[2][0]), d(result.m[0][0]));
+ pitch = std::atan2(-d(result.m[1][2]), d(result.m[1][1]));
+ roll = std::asin(d(result.m[1][0]));
}
steamvr_dialog::steamvr_dialog()
@@ -321,7 +334,7 @@ void steamvr_dialog::doCancel()
QString device_spec::to_string() const
{
- return QStringLiteral("<%1> %2 [%3]").arg(type).arg(model).arg(serial);
+ return QStringLiteral("<%1> %2 [%3]").arg(type, model, serial);
}
OPENTRACK_DECLARE_TRACKER(steamvr, steamvr_dialog, steamvr_metadata)
diff --git a/tracker-steamvr/steamvr.hpp b/tracker-steamvr/steamvr.hpp
index 479f517f..61da2e05 100644
--- a/tracker-steamvr/steamvr.hpp
+++ b/tracker-steamvr/steamvr.hpp
@@ -1,26 +1,24 @@
#pragma once
-#include "api/plugin-api.hpp"
+
#include "ui_dialog.h"
+#include "api/plugin-api.hpp"
#include "options/options.hpp"
-#include "compat/euler.hpp"
-
-#include <openvr.h>
-
-#include <cmath>
-#include <memory>
#include <tuple>
+#include <climits>
#include <QString>
#include <QMutex>
-#include <QMutexLocker>
#include <QList>
+#include <openvr.h>
+
using namespace options;
-using error_t = vr::EVRInitError;
+
+using vr_error_t = vr::EVRInitError;
using vr_t = vr::IVRSystem*;
-using tt = std::tuple<vr_t, error_t>;
+using tt = std::tuple<vr_t, vr_error_t>;
using pose_t = vr::TrackedDevicePose_t;
using origin = vr::ETrackingUniverseOrigin;
@@ -51,11 +49,11 @@ struct device_list final
const QList<device_spec>& devices() const { return device_specs; }
static never_inline maybe_pose get_pose(int k);
- static QString strerror(error_t error);
- static constexpr inline unsigned max_devices = vr::k_unMaxTrackedDeviceCount;
+ static QString error_string(vr_error_t error);
+ static constexpr unsigned max_devices = vr::k_unMaxTrackedDeviceCount;
template<typename F>
- friend auto with_vr_lock(F&& fun) -> decltype(fun(vr_t(), error_t()));
+ friend auto with_vr_lock(F&& fun) -> decltype(fun(vr_t(), vr_error_t()));
private:
QList<device_spec> device_specs;
@@ -69,8 +67,10 @@ class steamvr : public QObject, public ITracker
{
Q_OBJECT
- using error_t = vr::EVRInitError;
- using vr_t = vr::IVRSystem*;
+ static void matrix_to_euler(double& yaw, double& pitch, double& roll, const vr::HmdMatrix34_t& result);
+
+ settings s;
+ unsigned device_index{UINT_MAX};
public:
steamvr();
@@ -78,15 +78,6 @@ public:
module_status start_tracker(QFrame *) override;
void data(double *data) override;
bool center() override;
-
-private:
- static void matrix_to_euler(double& yaw, double& pitch, double& roll, const vr::HmdMatrix34_t& result);
-
- settings s;
- int device_index;
-
- using rmat = euler::rmat;
- using euler_t = euler::euler_t;
};
class steamvr_dialog : public ITrackerDialog
@@ -106,7 +97,8 @@ private slots:
class steamvr_metadata : public Metadata
{
-public:
- QString name() override { return otr_tr("Valve SteamVR"); }
+ Q_OBJECT
+
+ QString name() override { return tr("Valve SteamVR"); }
QIcon icon() override { return QIcon(":/images/rift_tiny.png"); }
};
diff --git a/tracker-test/lang/de_DE.ts b/tracker-test/lang/de_DE.ts
new file mode 100644
index 00000000..e11372b6
--- /dev/null
+++ b/tracker-test/lang/de_DE.ts
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>test_metadata</name>
+ <message>
+ <source>Test tracker</source>
+ <translation>Test-Tracker</translation>
+ </message>
+</context>
+<context>
+ <name>test_ui</name>
+ <message>
+ <source>Sine wave</source>
+ <translation>Sinus-Welle</translation>
+ </message>
+ <message>
+ <source>Pressing &quot;Abort&quot; will immediately crash the application.</source>
+ <translation>Beim Klick auf „Abbrechen“ wird die Anwendung sofort abstürzen.</translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-test/lang/nl_NL.ts b/tracker-test/lang/nl_NL.ts
index 2917d26b..b4150273 100644
--- a/tracker-test/lang/nl_NL.ts
+++ b/tracker-test/lang/nl_NL.ts
@@ -2,6 +2,13 @@
<!DOCTYPE TS>
<TS version="2.1" language="nl_NL">
<context>
+ <name>test_metadata</name>
+ <message>
+ <source>Test tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>test_ui</name>
<message>
<source>Sine wave</source>
diff --git a/tracker-test/lang/ru_RU.ts b/tracker-test/lang/ru_RU.ts
index ca7d0ce3..c69b3728 100644
--- a/tracker-test/lang/ru_RU.ts
+++ b/tracker-test/lang/ru_RU.ts
@@ -2,6 +2,13 @@
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
<context>
+ <name>test_metadata</name>
+ <message>
+ <source>Test tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>test_ui</name>
<message>
<source>Sine wave</source>
diff --git a/tracker-test/lang/stub.ts b/tracker-test/lang/stub.ts
index ddba8441..78f647f5 100644
--- a/tracker-test/lang/stub.ts
+++ b/tracker-test/lang/stub.ts
@@ -2,6 +2,13 @@
<!DOCTYPE TS>
<TS version="2.1">
<context>
+ <name>test_metadata</name>
+ <message>
+ <source>Test tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>test_ui</name>
<message>
<source>Sine wave</source>
diff --git a/tracker-test/lang/zh_CN.ts b/tracker-test/lang/zh_CN.ts
index ddba8441..5df28f2f 100644
--- a/tracker-test/lang/zh_CN.ts
+++ b/tracker-test/lang/zh_CN.ts
@@ -1,6 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>test_metadata</name>
+ <message>
+ <source>Test tracker</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
<context>
<name>test_ui</name>
<message>
diff --git a/tracker-test/test.cpp b/tracker-test/test.cpp
index d37d7373..3ca47ae1 100644
--- a/tracker-test/test.cpp
+++ b/tracker-test/test.cpp
@@ -15,73 +15,49 @@
#include <cmath>
#include <QDebug>
-const double test_tracker::incr[6] =
+static const double incr[3] =
{
- 50, 40, 80,
- 70, 5, 3
+ 10, 5, 3
};
-test_tracker::test_tracker() :
- last_x { 0, 0, 0, 0, 0, 0 }
-{
-}
+static const double max_values[3] = {
+ 180, 2, 3,
+};
-test_tracker::~test_tracker()
-{
-}
+test_tracker::test_tracker() = default;
+test_tracker::~test_tracker() = default;
module_status test_tracker::start_tracker(QFrame*)
{
t.start();
-
- return status_ok();
+ return {};
}
-#ifdef EMIT_NAN
-# include <cstdlib>
-#endif
-
void test_tracker::data(double *data)
{
const double dt = t.elapsed_seconds();
t.start();
-#ifdef EMIT_NAN
- if ((rand()%4) == 0)
+ for (int i = 0; i < 3; i++)
{
- for (int i = 0; i < 6; i++)
- data[i] = 0./0.;
+ double last_ = last[i];
+ double max = max_values[i] * 2;
+ double incr_ = incr[i];
+ double x = fmod(last_ + incr_ * dt, max);
+ last[i] = x;
+ if (x > max_values[i])
+ x = -max + x;
+ data[i+3] = x;
}
- else
-#endif
- for (int i = 0; i < 6; i++)
- {
- double x = last_x[i] + incr[i] * dt;
- if (x > 180)
- x = -360 + x;
- else if (x < -180)
- x = 360 + x;
- x = copysign(fmod(fabs(x), 360), x);
- last_x[i] = x;
-
- if (i >= 3)
- {
- data[i] = x;
- }
- else
- {
- data[i] = x * 100/180.;
- }
- }
}
-test_dialog::test_dialog()
+test_dialog::test_dialog() // NOLINT(cppcoreguidelines-pro-type-member-init)
{
ui.setupUi(this);
connect(ui.buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* btn) {
if (btn == ui.buttonBox->button(QDialogButtonBox::Abort))
- *(volatile int*)0 = 0;
+ *(volatile int*)nullptr /*NOLINT*/ = 0;
});
connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
diff --git a/tracker-test/test.h b/tracker-test/test.h
index c7f19698..320145ad 100644
--- a/tracker-test/test.h
+++ b/tracker-test/test.h
@@ -2,7 +2,6 @@
#include "ui_test.h"
#include "api/plugin-api.hpp"
#include "compat/timer.hpp"
-#include "compat/macros.hpp"
#include <cmath>
@@ -15,8 +14,7 @@ public:
void data(double *data) override;
private:
- static const double incr[6];
- double last_x[6];
+ double last[6] {};
Timer t;
};
@@ -36,8 +34,9 @@ private slots:
class test_metadata : public Metadata
{
-public:
- QString name() { return _("Testing - sine wave"); }
- QIcon icon() { return QIcon(":/images/opentrack.png"); }
+ Q_OBJECT
+
+ QString name() override { return tr("Test tracker"); }
+ QIcon icon() override { return QIcon(":/images/opentrack.png"); }
};
diff --git a/tracker-tobii-eyex/CMakeLists.txt b/tracker-tobii-eyex/CMakeLists.txt
deleted file mode 100644
index eead67bf..00000000
--- a/tracker-tobii-eyex/CMakeLists.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-if(WIN32)
- set(SDK_TOBII_EYEX "" CACHE PATH "")
- if(SDK_TOBII_EYEX)
- otr_module(tracker-tobii)
- set(tobii-libdir ${SDK_TOBII_EYEX}/lib/x86/)
- set(tobii-dll "${tobii-libdir}/Tobii.EyeX.Client.dll")
- # we only care about the .lib for MSVC++ build anyway
- target_link_libraries(opentrack-tracker-tobii "${tobii-libdir}/Tobii.EyeX.Client.lib")
- # we only ever use the C headers due to Microsoft CRT ABI incompatibility with GNU
- target_include_directories(opentrack-tracker-tobii SYSTEM PUBLIC "${SDK_TOBII_EYEX}/include/eyex")
-
- install(FILES "${tobii-dll}" DESTINATION "${opentrack-hier-pfx}" PERMISSIONS ${opentrack-perms-exec})
- if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
- file(TO_CMAKE_PATH "$ENV{SystemRoot}" sysroot)
- if (IS_DIRECTORY "${sysroot}/SysWOW64")
- set(src "${sysroot}/SysWOW64")
- else()
- set(src "${sysroot}/System32")
- endif()
- install(FILES "${src}/msvcp110.dll" DESTINATION ${opentrack-hier-pfx} PERMISSIONS ${opentrack-perms-exec})
- install(FILES "${src}/msvcr110.dll" DESTINATION ${opentrack-hier-pfx} PERMISSIONS ${opentrack-perms-exec})
- endif()
- endif()
-endif()
diff --git a/tracker-tobii-eyex/images/tobii-eyex-logo.png b/tracker-tobii-eyex/images/tobii-eyex-logo.png
deleted file mode 100644
index e01bc1ae..00000000
--- a/tracker-tobii-eyex/images/tobii-eyex-logo.png
+++ /dev/null
Binary files differ
diff --git a/tracker-tobii-eyex/lang/zh_CN.ts b/tracker-tobii-eyex/lang/zh_CN.ts
deleted file mode 100644
index 57899430..00000000
--- a/tracker-tobii-eyex/lang/zh_CN.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1">
-<context>
- <name>tobii_eyex_dialog_widgets</name>
- <message>
- <source>Tracker options</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Tracking settings</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Accumulative mode shifts the view toward a target that may be offscreen then fixes upon it.
-On the other hand, the snap mode allows for a quick glance outside the field of vision.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Tracking mode</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Accumulative mode settings</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Screen edge length</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Speed</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Max yaw</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Max pitch</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Position output</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Enabled</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Snap mode settings</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-</TS>
diff --git a/tracker-tobii-eyex/tobii-eyex-dialog.cpp b/tracker-tobii-eyex/tobii-eyex-dialog.cpp
deleted file mode 100644
index 807542e1..00000000
--- a/tracker-tobii-eyex/tobii-eyex-dialog.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-#include "tobii-eyex-dialog.hpp"
-
-tobii_eyex_dialog::tobii_eyex_dialog()
-{
- ui.setupUi(this);
-
- connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &tobii_eyex_dialog::do_ok);
- connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &tobii_eyex_dialog::do_cancel);
-
- ui.tracking_mode->addItem("Snap", tobii_snap);
- ui.tracking_mode->addItem("Accumulative", tobii_acc);
-
- tie_setting(s.mode, ui.tracking_mode);
-}
-
-void tobii_eyex_dialog::do_ok()
-{
- s.b->save();
- close();
-}
-
-void tobii_eyex_dialog::do_cancel()
-{
- close();
-}
diff --git a/tracker-tobii-eyex/tobii-eyex-dialog.hpp b/tracker-tobii-eyex/tobii-eyex-dialog.hpp
deleted file mode 100644
index 67cab885..00000000
--- a/tracker-tobii-eyex/tobii-eyex-dialog.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-
-#include "tobii-settings.hpp"
-
-#include "api/plugin-api.hpp"
-#include "ui_tobii-eyex-dialog.h"
-#include <QObject>
-
-class tobii_eyex_dialog final : public ITrackerDialog
-{
- Q_OBJECT
- Ui::tobii_eyex_dialog_widgets ui;
- settings s;
-private slots:
- void do_ok();
- void do_cancel();
-public:
- tobii_eyex_dialog();
- void register_tracker(ITracker*) override {}
- void unregister_tracker() override {}
-};
diff --git a/tracker-tobii-eyex/tobii-eyex-dialog.ui b/tracker-tobii-eyex/tobii-eyex-dialog.ui
deleted file mode 100644
index a6aecafb..00000000
--- a/tracker-tobii-eyex/tobii-eyex-dialog.ui
+++ /dev/null
@@ -1,484 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>tobii_eyex_dialog_widgets</class>
- <widget class="QWidget" name="tobii_eyex_dialog_widgets">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>659</width>
- <height>471</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>Tracker options</string>
- </property>
- <property name="windowIcon">
- <iconset resource="tobii-eyex-res.qrc">
- <normaloff>:/images/tobii-eyex-logo.png</normaloff>:/images/tobii-eyex-logo.png</iconset>
- </property>
- <layout class="QGridLayout" name="gridLayout_4">
- <property name="bottomMargin">
- <number>4</number>
- </property>
- <item row="0" column="0">
- <widget class="QGroupBox" name="groupBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Tracking settings</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QLabel" name="label_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>617</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Accumulative mode shifts the view toward a target that may be offscreen then fixes upon it.
-On the other hand, the snap mode allows for a quick glance outside the field of vision.</string>
- </property>
- <property name="textFormat">
- <enum>Qt::PlainText</enum>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QFrame" name="frame">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <property name="leftMargin">
- <number>4</number>
- </property>
- <property name="topMargin">
- <number>4</number>
- </property>
- <property name="rightMargin">
- <number>4</number>
- </property>
- <property name="bottomMargin">
- <number>4</number>
- </property>
- <property name="horizontalSpacing">
- <number>9</number>
- </property>
- <property name="verticalSpacing">
- <number>4</number>
- </property>
- <item row="0" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Tracking mode</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QComboBox" name="tracking_mode">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>10</width>
- <height>0</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QGroupBox" name="groupBox_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Accumulative mode settings</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="0">
- <widget class="QLabel" name="label_3">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Screen edge length</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QSlider" name="acc_inv_deadzone_slider">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
- <horstretch>9</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>20</number>
- </property>
- <property name="pageStep">
- <number>1</number>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QLabel" name="acc_inv_deadzone_label">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
- <horstretch>10</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_4">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Speed</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_6">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Max yaw</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QSlider" name="acc_speed_slider">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
- <horstretch>9</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximum">
- <number>100</number>
- </property>
- <property name="pageStep">
- <number>1</number>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_7">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Max pitch</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QComboBox" name="acc_max_yaw">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QComboBox" name="acc_max_pitch">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item row="4" column="0">
- <widget class="QLabel" name="label_11">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Position output</string>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QCheckBox" name="acc_pos_output">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Enabled</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QGroupBox" name="groupBox_3">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Snap mode settings</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="2" column="1" rowspan="2">
- <widget class="QComboBox" name="snap_max_yaw">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item row="3" column="0" rowspan="2">
- <widget class="QLabel" name="label_10">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Max pitch</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_9">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Max yaw</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_8">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Speed</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QSlider" name="snap_inv_deazone_slider">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
- <horstretch>8</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>20</number>
- </property>
- <property name="pageStep">
- <number>1</number>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QComboBox" name="snap_max_pitch">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QLabel" name="snap_inv_deadzone_label">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>10</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QSlider" name="snap_speed_slider">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
- <horstretch>8</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximum">
- <number>100</number>
- </property>
- <property name="pageStep">
- <number>1</number>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_5">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Screen edge length</string>
- </property>
- </widget>
- </item>
- <item row="5" column="1">
- <widget class="QCheckBox" name="snap_pos_output">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Enabled</string>
- </property>
- </widget>
- </item>
- <item row="5" column="0">
- <widget class="QLabel" name="label_12">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Position output</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="3" column="0">
- <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::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <resources>
- <include location="tobii-eyex-res.qrc"/>
- </resources>
- <connections/>
-</ui>
diff --git a/tracker-tobii-eyex/tobii-eyex.cpp b/tracker-tobii-eyex/tobii-eyex.cpp
deleted file mode 100644
index a559c8dd..00000000
--- a/tracker-tobii-eyex/tobii-eyex.cpp
+++ /dev/null
@@ -1,312 +0,0 @@
-#include "tobii-eyex.hpp"
-#include "compat/math-imports.hpp"
-
-#include <cstdlib>
-#include <cstdio>
-
-#include <QDebug>
-#include <QMutexLocker>
-#include <QMessageBox>
-
-//#define TOBII_EYEX_DEBUG_PRINTF
-#define TOBII_EYEX_VERBOSE_PRINTF
-
-#ifdef TOBII_EYEX_VERBOSE_PRINTF
-# define dbg_verbose(msg) (qDebug() << "tobii-eyex:" << (msg))
-#else
-# define dbg_verbose(msg) (QMessageLogger().noDebug() << (msg))
-#endif
-
-#ifdef TOBII_EYEX_DEBUG_PRINTF
-# define dbg_debug(msg) (qDebug() << "tobii-eyex:" << (msg))
-#else
-# define dbg_debug(msg) (QMessageLogger().noDebug() << (msg))
-#endif
-
-#define dbg_notice(msg) (qDebug() << "tobii-eyex:" << (msg))
-
-std::atomic_flag tobii_eyex_tracker::atexit_done = ATOMIC_FLAG_INIT;
-
-static inline tobii_eyex_tracker& to_self(TX_USERPARAM param)
-{
- return *reinterpret_cast<tobii_eyex_tracker*>(param);
-}
-
-tobii_eyex_tracker::tobii_eyex_tracker() :
- dev_ctx(TX_EMPTY_HANDLE),
- conn_state_changed_ticket(TX_INVALID_TICKET),
- event_handler_ticket(TX_INVALID_TICKET),
- state_snapshot(TX_EMPTY_HANDLE),
- display_state(TX_EMPTY_HANDLE),
- yaw(0),
- pitch(0),
- do_center(false)
-{
-}
-
-void tobii_eyex_tracker::call_tx_deinit()
-{
- dbg_notice("uninitialize in atexit at _fini time");
- (void) txUninitializeEyeX();
-}
-
-tobii_eyex_tracker::~tobii_eyex_tracker()
-{
- dbg_verbose("dtor");
-
- (void) txDisableConnection(dev_ctx);
- (void) txReleaseObject(&state_snapshot);
-
- bool status = true;
- status &= txShutdownContext(dev_ctx, TX_CLEANUPTIMEOUT_FORCEIMMEDIATE, TX_FALSE) == TX_RESULT_OK;
- status &= txReleaseContext(&dev_ctx) == TX_RESULT_OK;
-
- // the API cleanup function needs to be called exactly once over image lifetime.
- // client software communicates with a service and a desktop program.
- // API is ambiguous as to what happens if the image doesn't call it or crashes.
- if (!atexit_done.test_and_set())
- std::atexit(call_tx_deinit);
-
- if (!status)
- dbg_notice("tobii-eyex: can't shutdown properly");
-}
-
-bool tobii_eyex_tracker::register_state_snapshot(TX_CONTEXTHANDLE dev_ctx, TX_HANDLE* state_snapshot_ptr)
-{
- TX_HANDLE handle = TX_EMPTY_HANDLE;
- TX_GAZEPOINTDATAPARAMS params = { TX_GAZEPOINTDATAMODE_LIGHTLYFILTERED };
-
- bool status = true;
-
- status &= txCreateGlobalInteractorSnapshot(dev_ctx, client_id, state_snapshot_ptr, &handle) == TX_RESULT_OK;
- status &= txCreateGazePointDataBehavior(handle, &params) == TX_RESULT_OK;
-
- (void) txReleaseObject(&handle);
-
- return status;
-}
-
-void tobii_eyex_tracker::process_display_state(TX_HANDLE display_state_handle)
-{
- TX_SIZE2 screen_res;
-
- if (txGetStateValueAsSize2(display_state_handle, TX_STATEPATH_EYETRACKINGSCREENBOUNDS, &screen_res) == TX_RESULT_OK)
- {
- dbg_verbose("got display resolution") << screen_res.Width << screen_res.Height;
-
- QMutexLocker l(&global_state_mtx);
-
- dev_state.display_res_x = screen_res.Width;
- dev_state.display_res_y = screen_res.Height;
- }
- else
- dbg_notice("can't get display resolution");
-}
-
-void tobii_eyex_tracker::display_state_handler(TX_CONSTHANDLE async_data_handle, TX_USERPARAM param)
-{
- tobii_eyex_tracker& self = to_self(param);
-
- TX_RESULT result = TX_RESULT_UNKNOWN;
- TX_HANDLE state = TX_EMPTY_HANDLE;
-
- if (txGetAsyncDataResultCode(async_data_handle, &result) == TX_RESULT_OK &&
- txGetAsyncDataContent(async_data_handle, &state) == TX_RESULT_OK)
- {
- self.process_display_state(state);
- txReleaseObject(&state);
- }
- else
- dbg_notice("error in display state handler");
-}
-
-void tobii_eyex_tracker::snapshot_committed_handler(TX_CONSTHANDLE async_data_handle, TX_USERPARAM)
-{
- TX_RESULT result = TX_RESULT_UNKNOWN;
- txGetAsyncDataResultCode(async_data_handle, &result);
-
- if (!(result == TX_RESULT_OK || result == TX_RESULT_CANCELLED))
- dbg_notice("snapshot bad result code") << result;
-}
-
-void tobii_eyex_tracker::connection_state_change_handler(TX_CONNECTIONSTATE state, TX_USERPARAM param)
-{
- tobii_eyex_tracker& self = to_self(param);
-
- switch (state)
- {
- case TX_CONNECTIONSTATE_CONNECTED:
- {
- bool status = txCommitSnapshotAsync(self.state_snapshot, snapshot_committed_handler, param) == TX_RESULT_OK;
- if (!status)
- dbg_notice("connected but failed to initialize data stream");
- else
- {
- txGetStateAsync(self.dev_ctx, TX_STATEPATH_EYETRACKINGSCREENBOUNDS, display_state_handler, param);
- dbg_notice("connected, data stream ok");
- }
- }
- break;
- case TX_CONNECTIONSTATE_DISCONNECTED:
- dbg_notice("connection state is now disconnected");
- break;
- case TX_CONNECTIONSTATE_TRYINGTOCONNECT:
- dbg_verbose("trying to establish connection");
- break;
- case TX_CONNECTIONSTATE_SERVERVERSIONTOOLOW:
- dbg_notice("installed driver version too low");
- break;
- case TX_CONNECTIONSTATE_SERVERVERSIONTOOHIGH:
- dbg_notice("new driver came up, we need to update sdk");
- break;
- }
-}
-
-void tobii_eyex_tracker::gaze_data_handler(TX_HANDLE gaze_data_handle)
-{
- TX_GAZEPOINTDATAEVENTPARAMS params;
-
- if (txGetGazePointDataEventParams(gaze_data_handle, &params) == TX_RESULT_OK)
- {
- {
- QMutexLocker l(&global_state_mtx);
-
- if (params.Timestamp > dev_state.last_timestamp &&
- dev_state.display_res_x > 0 &&
- // the API allows for events outside screen bounds to e.g. detect looking at keyboard.
- // closer to the screen bounds, the values get less accurate.
- // ignore events outside the screen bounds.
- params.X >= 0 && params.X < dev_state.display_res_x &&
- params.Y >= 0 && params.Y < dev_state.display_res_y)
- {
- dev_state.last_timestamp = params.Timestamp;
- dev_state.px = params.X;
- dev_state.py = params.Y;
-
-#ifdef TOBII_EYEX_DEBUG_PRINTF
- char buf[256] = {0};
- (void) std::sprintf(buf, "gaze data: (%.1f, %.1f)", params.X, params.Y);
- dbg_debug(buf);
-#endif
-
- dev_state.fresh = true;
- }
- }
- }
- else
- {
- dbg_notice("failed to interpret gaze data event packet");
- }
-}
-
-void tobii_eyex_tracker::event_handler(TX_CONSTHANDLE async_data_handle, TX_USERPARAM param)
-{
- tobii_eyex_tracker& self = to_self(param);
-
- TX_HANDLE event_handle = TX_EMPTY_HANDLE;
- TX_HANDLE behavior_handle = TX_EMPTY_HANDLE;
-
- txGetAsyncDataContent(async_data_handle, &event_handle);
-
- if (txGetEventBehavior(event_handle, &behavior_handle, TX_BEHAVIORTYPE_GAZEPOINTDATA) == TX_RESULT_OK)
- {
- self.gaze_data_handler(behavior_handle);
- txReleaseObject(&behavior_handle);
- }
-
- txReleaseObject(&event_handle);
-}
-
-module_status tobii_eyex_tracker::start_tracker(QFrame*)
-{
- bool status = true;
-
- status &= txInitializeEyeX(TX_EYEXCOMPONENTOVERRIDEFLAG_NONE, nullptr, nullptr, nullptr, nullptr) == TX_RESULT_OK;
- status &= txCreateContext(&dev_ctx, TX_FALSE) == TX_RESULT_OK;
- status &= register_state_snapshot(dev_ctx, &state_snapshot);
- status &= txRegisterConnectionStateChangedHandler(dev_ctx, &conn_state_changed_ticket, connection_state_change_handler, reinterpret_cast<TX_USERPARAM>(this)) == TX_RESULT_OK;
- status &= txRegisterEventHandler(dev_ctx, &event_handler_ticket, event_handler, reinterpret_cast<TX_USERPARAM>(this)) == TX_RESULT_OK;
- status &= txEnableConnection(dev_ctx) == TX_RESULT_OK;
-
- if (!status)
- return error(otr_tr("Connection can't be established. device missing?"));
- else
- return status_ok();
-}
-
-tobii_eyex_tracker::num tobii_eyex_tracker::gain(num x)
-{
- return 1;
-}
-
-static inline double signum(double x)
-{
- return !(x < 0) - (x < 0);
-}
-
-void tobii_eyex_tracker::data(double* data)
-{
- TX_REAL px, py, dw, dh, x_, y_;
- bool fresh;
-
- {
- QMutexLocker l(&global_state_mtx);
-
- if (!dev_state.is_valid())
- return;
-
- px = dev_state.px;
- py = dev_state.py;
- dw = dev_state.display_res_x;
- dh = dev_state.display_res_y;
-
- fresh = dev_state.fresh;
- dev_state.fresh = false;
- }
-
- x_ = (px-dw/2.) / (dw/2.);
- y_ = (py-dh/2.) / (dh/2.);
-
- data[TX] = x_ * 50;
- data[TY] = y_ * -50;
-
- if (fresh)
- {
- const double dt = t.elapsed_seconds();
- t.start();
-
- using std::fabs;
-
- constexpr double max_yaw = 45, max_pitch = 30;
- constexpr double c_yaw = 3;
- constexpr double c_pitch = c_yaw * max_pitch / max_yaw;
-
- const double yaw_delta = gain(fabs(x_)) * signum(x_) * c_yaw * dt;
- const double pitch_delta = gain(fabs(y_)) * signum(y_) * c_pitch * dt;
-
- yaw += yaw_delta;
- pitch += pitch_delta;
-
- yaw = clamp(yaw, -max_yaw, max_yaw);
- pitch = clamp(pitch, -max_pitch, max_pitch);
- }
-
- if (do_center)
- {
- do_center = false;
- yaw = 0;
- pitch = 0;
- }
-
- data[Yaw] = yaw;
- data[Pitch] = pitch;
- data[Roll] = 0;
- data[TZ] = 0; // XXX TODO
-
- // tan(x) in 0->.7 is almost linear. we don't need to adjust.
- // .7 is 40 degrees which is already quite a lot from the monitor.
-}
-
-#include "tobii-eyex-dialog.hpp"
-
-OPENTRACK_DECLARE_TRACKER(tobii_eyex_tracker, tobii_eyex_dialog, tobii_eyex_metadata)
diff --git a/tracker-tobii-eyex/tobii-eyex.hpp b/tracker-tobii-eyex/tobii-eyex.hpp
deleted file mode 100644
index 170e74c1..00000000
--- a/tracker-tobii-eyex/tobii-eyex.hpp
+++ /dev/null
@@ -1,87 +0,0 @@
-#pragma once
-
-/* Copyright (c) 2016 Stanislaw Halik <sthalik@misaki.pl>
-
- * 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-settings.hpp"
-
-#include "api/plugin-api.hpp"
-#include "options/options.hpp"
-using namespace options;
-#include "compat/timer.hpp"
-
-#include <EyeX.h>
-
-#include <functional>
-#include <atomic>
-
-#include <QObject>
-#include <QMutex>
-
-class tobii_eyex_tracker : public ITracker
-{
-public:
- tobii_eyex_tracker();
- ~tobii_eyex_tracker() override;
- module_status start_tracker(QFrame *) override;
- void data(double *data) override;
- bool center() override
- {
- do_center = true;
- return true;
- }
-private:
- static constexpr inline const char* const client_id = "opentrack-tobii-eyex";
-
- static void call_tx_deinit();
-
- static bool register_state_snapshot(TX_CONTEXTHANDLE ctx, TX_HANDLE* state_snapshot_ptr);
- static std::atomic_flag atexit_done;
- static void TX_CALLCONVENTION connection_state_change_handler(TX_CONNECTIONSTATE state, TX_USERPARAM param);
- static void TX_CALLCONVENTION event_handler(TX_CONSTHANDLE async_data_handle, TX_USERPARAM param);
- void gaze_data_handler(TX_HANDLE gaze_data_handle);
- static void TX_CALLCONVENTION snapshot_committed_handler(TX_CONSTHANDLE async_data_handle, TX_USERPARAM param);
- static void TX_CALLCONVENTION display_state_handler(TX_CONSTHANDLE async_data_handle, TX_USERPARAM param);
- void process_display_state(TX_HANDLE display_state_handle);
-
- using num = double;
-
- num gain(num x);
-
- TX_CONTEXTHANDLE dev_ctx;
- TX_TICKET conn_state_changed_ticket;
- TX_TICKET event_handler_ticket;
- TX_HANDLE state_snapshot;
- TX_HANDLE display_state;
-
- QMutex global_state_mtx;
- settings s;
- Timer t;
-
- struct state
- {
- TX_REAL display_res_x, display_res_y;
- TX_REAL px, py;
- TX_REAL last_timestamp;
- bool fresh;
-
- state() : display_res_x(-1), display_res_y(-1), px(-1), py(-1), last_timestamp(0), fresh(false) {}
- bool is_valid() const { return !(display_res_x < 0 || px < 0); }
- } dev_state;
-
- double yaw, pitch;
- std::atomic<bool> do_center;
-};
-
-class tobii_eyex_metadata : public Metadata
-{
-public:
- QString name() { return QString("Tobii EyeX"); }
- QIcon icon() { return QIcon(":/images/tobii-eyex-logo.png"); }
-};
-
diff --git a/tracker-tobii-eyex/tobii-settings.hpp b/tracker-tobii-eyex/tobii-settings.hpp
deleted file mode 100644
index a486306d..00000000
--- a/tracker-tobii-eyex/tobii-settings.hpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#pragma once
-
-#include "options/options.hpp"
-using namespace options;
-
-enum tobii_mode
-{
- tobii_snap = 0xf00d,
- tobii_acc = 0xacc,
-};
-
-enum max_yaw
-{
- y10, y15, y20, y30, y45,
-};
-
-enum max_pitch
-{
- p10, p15, p25, p35,
-};
-
-struct settings final : public opts
-{
- value<tobii_mode> mode { b, "mode", tobii_snap };
-
- value<slider_value> snap_speed {b, "snap-speed", slider_value(.1, .05, 1)},
- snap_inv_dz {b, "snap-screen-edge-length", slider_value(.35, .1, .5)};
- value<slider_value> acc_speed {b, "acc-speed", slider_value(.1, .05, 1)},
- acc_dz_len {b, "acc-screen-edge-length", slider_value(.1, .1, 1)};
- value<max_yaw> snap_yaw {b, "snap-max-yaw", y20},
- acc_yaw {b, "acc-max-yaw", y20};
- value<max_pitch> snap_pitch {b, "snap-max-pitch", p15},
- acc_pitch {b, "acc-max-pitch", p15};
-
- settings() : opts("tobii-eyex") {}
-};
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
new file mode 100644
index 00000000..3502b37b
--- /dev/null
+++ b/tracker-tobii/tobii_logo.png
Binary files differ
diff --git a/tracker-trackhat/CMakeLists.txt b/tracker-trackhat/CMakeLists.txt
new file mode 100644
index 00000000..483bf4c9
--- /dev/null
+++ b/tracker-trackhat/CMakeLists.txt
@@ -0,0 +1,16 @@
+if(WIN32)
+ include(opentrack-opencv)
+ find_package(OpenCV QUIET)
+ if(OpenCV_FOUND)
+ foreach(k core imgproc)
+ otr_install_lib("opencv_${k}" "${opentrack-libexec}")
+ endforeach()
+ set(SDK_TRACKHAT_SENSOR CACHE PATH "")
+ if(SDK_TRACKHAT_SENSOR)
+ include_directories("${SDK_TRACKHAT_SENSOR}/include" ${OpenCV_INCLUDE_DIRS})
+ link_directories("${SDK_TRACKHAT_SENSOR}/lib")
+ link_libraries(opencv_imgproc opencv_core opentrack-tracker-pt-base track-hat)
+ otr_module(tracker-trackhat)
+ endif()
+ endif()
+endif()
diff --git a/tracker-trackhat/camera.cpp b/tracker-trackhat/camera.cpp
new file mode 100644
index 00000000..d7079a96
--- /dev/null
+++ b/tracker-trackhat/camera.cpp
@@ -0,0 +1,140 @@
+#include "trackhat.hpp"
+#include "compat/sleep.hpp"
+#include <algorithm>
+#include <cstdio>
+
+namespace trackhat_impl {
+
+TH_ErrorCode log_error(TH_ErrorCode error, const char* source,
+ const char* file, int line, const char* function)
+{
+ if (error == TH_ERROR_DEVICE_ALREADY_OPEN)
+ error = TH_SUCCESS;
+ if (error)
+ {
+ auto logger = QMessageLogger(file, line, function).warning();
+ logger << "tracker/trackhat: error" << (void*)-error << "in" << source;
+ }
+ return error;
+}
+
+} // ns trackhat_impl
+
+pt_camera::result trackhat_camera::get_info() const
+{
+ return {true, get_desired() };
+}
+
+pt_camera_info trackhat_camera::get_desired() const
+{
+ pt_camera_info ret = {};
+
+ ret.fov = sensor_fov;
+ ret.fps = 250;
+ ret.res_x = sensor_size;
+ ret.res_y = sensor_size;
+
+ return ret;
+}
+
+QString trackhat_camera::get_desired_name() const
+{
+ return QStringLiteral("TrackHat sensor v1");
+}
+
+QString trackhat_camera::get_active_name() const
+{
+ return get_desired_name();
+}
+
+void trackhat_camera::set_fov(pt_camera::f) {}
+void trackhat_camera::show_camera_settings() {}
+
+trackhat_camera::trackhat_camera()
+{
+ s.set_raii_dtor_state(false);
+ t.set_raii_dtor_state(false);
+
+ for (auto* slider : { &t.exposure, &t.gain, /*&t.threshold,*/ })
+ {
+ QObject::connect(slider, options::value_::value_changed<options::slider_value>(),
+ &sig, &trackhat_impl::setting_receiver::settings_changed,
+ Qt::DirectConnection);
+ }
+}
+
+trackhat_camera::~trackhat_camera()
+{
+ stop();
+}
+
+pt_camera::result trackhat_camera::get_frame(pt_frame& frame_)
+{
+ if (!device.ensure_connected())
+ goto error;
+
+ if (sig.test_and_clear() && !init_regs())
+ goto error;
+
+ set_pt_options();
+
+ {
+ trackHat_ExtendedPoints_t points;
+ if (!!th_check(trackHat_GetDetectedPointsExtended(&*device, &points)))
+ goto error;
+ auto& frame = *frame_.as<trackhat_frame>();
+ frame.init_points(points, t.min_pt_size, t.max_pt_size);
+
+ using trackhat_impl::led_state;
+ int count =
+ std::count_if(frame.points.cbegin(), frame.points.cend(),
+ [](const point& pt) { return pt.ok; });
+ led.update(&*device, *t.led,
+ count == 3
+ ? led_state::tracking
+ : led_state::not_tracking);
+ }
+
+ return {true, get_desired()};
+
+error:
+ stop();
+ return {false, {}};
+}
+
+static void log_handler(const char* file, int line, const char* function, char level, const char* str, size_t len)
+{
+ if (level != 'E')
+ return;
+ char file_[128];
+ snprintf(file_, std::size(file_), "trackhat/%s", file);
+ auto logger = QMessageLogger(file_, line, function).debug();
+ logger << "tracker/trackhat:";
+ logger.noquote() << QLatin1String(str, (int)len);
+}
+
+bool trackhat_camera::start(const pt_settings&)
+{
+ trackHat_SetDebugHandler(log_handler);
+
+ if constexpr(debug_mode)
+ trackHat_EnableDebugMode();
+ else
+ trackHat_DisableDebugMode();
+
+ if (!device.ensure_device_exists())
+ return false;
+
+ set_pt_options();
+
+ led.update(&*device, *t.led, trackhat_impl::led_state::stopped);
+
+ return true;
+}
+
+void trackhat_camera::stop()
+{
+ if (device)
+ led.update(&*device, *t.led, trackhat_impl::led_state::stopped);
+ device.disconnect();
+}
diff --git a/tracker-trackhat/dialog.cpp b/tracker-trackhat/dialog.cpp
new file mode 100644
index 00000000..4ef64f50
--- /dev/null
+++ b/tracker-trackhat/dialog.cpp
@@ -0,0 +1,165 @@
+#include "dialog.hpp"
+
+using namespace options;
+
+trackhat_dialog::trackhat_dialog()
+{
+ ui.setupUi(this);
+ poll_tracker_info();
+ poll_timer.setInterval(100);
+
+ const std::tuple<QString, model_type> model_types[] = {
+ { tr("Cap"), model_cap },
+ { tr("Clip (left)"), model_clip_left },
+ { tr("Clip (right)"), model_clip_right },
+ { tr("Mini Clip (left)"), model_mini_clip_left },
+ { tr("Mini Clip (right)"), model_mini_clip_right },
+ { tr("Custom"), model_mystery_meat },
+ };
+
+ ui.model_type->clear();
+
+ for (const auto& [name, type] : model_types)
+ ui.model_type->addItem(QIcon{}, name, (QVariant)(int)type);
+
+ // model
+
+ tie_setting(t.model, ui.model_type);
+ tie_setting(t.min_pt_size, ui.min_point_size);
+ tie_setting(t.max_pt_size, ui.max_point_size);
+ tie_setting(t.point_filter_limit, ui.point_filter_limit);
+
+ // exposure
+
+ ui.exposure_slider->setMinimum((int)t.exposure->min());
+ ui.exposure_slider->setMaximum((int)t.exposure->max());
+
+ tie_setting(t.exposure, ui.exposure_slider);
+ ui.exposure_label->setValue((int)*t.exposure);
+
+ connect(&t.exposure, value_::value_changed<slider_value>(), ui.exposure_label,
+ [this] { ui.exposure_label->setValue((int)*t.exposure); }, Qt::QueuedConnection);
+
+ // gain
+
+ ui.gain_slider->setMinimum((int)t.gain->min());
+ ui.gain_slider->setMaximum((int)t.gain->max());
+
+ tie_setting(t.gain, ui.gain_slider);
+ ui.gain_label->setValue((int)*t.gain);
+
+ connect(&t.gain, value_::value_changed<slider_value>(), ui.gain_label,
+ [this] { ui.gain_label->setValue((int)*t.gain); }, Qt::QueuedConnection);
+
+#if 0
+ // threshold
+
+ tie_setting(t.threshold, ui.threshold_slider);
+
+ ui.threshold_label->setValue((int)*t.threshold);
+
+ connect(&t.threshold, value_::value_changed<slider_value>(), ui.threshold_label, [=] {
+ ui.threshold_label->setValue((int)*t.threshold);
+ }, Qt::QueuedConnection);
+#endif
+
+ // point filter
+
+ ui.point_filter_limit_label->setValue(*t.point_filter_limit);
+ connect(&t.point_filter_limit, value_::value_changed<slider_value>(), ui.point_filter_limit_label,
+ [this] { ui.point_filter_limit_label->setValue(*t.point_filter_limit); }, Qt::QueuedConnection);
+
+ tie_setting(t.enable_point_filter, ui.enable_point_filter);
+ tie_setting(t.point_filter_coefficient, ui.point_filter_slider);
+ ui.point_filter_label->setValue(*t.point_filter_coefficient);
+
+ connect(&t.point_filter_coefficient, value_::value_changed<slider_value>(), ui.point_filter_label,
+ [this] { ui.point_filter_label->setValue(*t.point_filter_coefficient); }, Qt::QueuedConnection);
+
+ tie_setting(t.point_filter_deadzone, ui.point_filter_deadzone);
+ ui.point_filter_deadzone_label->setValue(*t.point_filter_deadzone);
+
+ connect(&t.point_filter_deadzone, value_::value_changed<slider_value>(), ui.point_filter_deadzone_label,
+ [this] { ui.point_filter_deadzone_label->setValue(*t.point_filter_deadzone); }, Qt::QueuedConnection);
+
+ // led
+
+ using trackhat_impl::led_mode;
+ ui.led_mode->setItemData(0, (int)led_mode::off);
+ ui.led_mode->setItemData(1, (int)led_mode::constant);
+ ui.led_mode->setItemData(2, (int)led_mode::dynamic);
+
+ tie_setting(t.led, ui.led_mode);
+
+ // stuff
+
+ connect(&poll_timer, &QTimer::timeout, this, &trackhat_dialog::poll_tracker_info);
+ connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &trackhat_dialog::doOK);
+ connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &trackhat_dialog::doCancel);
+}
+
+void trackhat_dialog::register_tracker(ITracker* tracker_)
+{
+ tracker = static_cast<Tracker_PT*>(tracker_);
+ poll_tracker_info();
+ poll_timer.start();
+}
+
+void trackhat_dialog::unregister_tracker()
+{
+ tracker = nullptr;
+ poll_tracker_info();
+ poll_timer.stop();
+ update_raw_data();
+}
+
+void trackhat_dialog::save()
+{
+ s.b->save();
+ t.b->save();
+}
+
+void trackhat_dialog::reload()
+{
+ s.b->reload();
+ t.b->reload();
+}
+
+void trackhat_dialog::doCancel() { reload(); close(); }
+void trackhat_dialog::doOK() { save(); close(); }
+
+trackhat_dialog::~trackhat_dialog()
+{
+}
+
+void trackhat_dialog::poll_tracker_info()
+{
+ if (!tracker)
+ ui.status_label->setText(tr("Status: Tracking stopped."));
+ else if (tracker->get_n_points() == 3)
+ ui.status_label->setText(tr("Status: %1 points detected. Good!").arg(tracker->get_n_points()));
+ else
+ ui.status_label->setText(tr("Status: %1 points detected. BAD!").arg(tracker->get_n_points()));
+ update_raw_data();
+}
+
+void trackhat_dialog::set_buttons_visible(bool x)
+{
+ ui.buttonBox->setVisible(x);
+ adjustSize();
+}
+void trackhat_dialog::update_raw_data()
+{
+ QLabel* labels[] = { ui.label_x, ui.label_y, ui.label_z, ui.label_yaw, ui.label_pitch, ui.label_roll };
+ if (tracker)
+ {
+ QString str; str.reserve(16);
+ double data[6] {};
+ tracker->data(data);
+ for (unsigned i = 0; i < std::size(labels); i++)
+ labels[i]->setText(str.sprintf("%.2f%s", data[i], i >= 3 ? "°" : " mm"));
+ }
+ else
+ for (QLabel* x : labels)
+ x->setText(QStringLiteral("-"));
+}
diff --git a/tracker-trackhat/dialog.hpp b/tracker-trackhat/dialog.hpp
new file mode 100644
index 00000000..35ca866b
--- /dev/null
+++ b/tracker-trackhat/dialog.hpp
@@ -0,0 +1,36 @@
+#pragma once
+#include "trackhat.hpp"
+#include "ui_dialog.h"
+#include "tracker-pt/ftnoir_tracker_pt.h"
+#include "api/plugin-api.hpp"
+#include <QTimer>
+
+class trackhat_dialog final : public ITrackerDialog
+{
+ Q_OBJECT
+
+protected:
+ Ui_trackhat_dialog ui;
+ Tracker_PT* tracker = nullptr;
+ QTimer poll_timer{this};
+
+ pt_settings s{trackhat_metadata::module_name};
+ trackhat_settings t;
+
+ void set_buttons_visible(bool x) override;
+ void update_raw_data();
+
+public:
+ trackhat_dialog();
+ ~trackhat_dialog() override;
+ void register_tracker(ITracker *tracker) override;
+ void unregister_tracker() override;
+ bool embeddable() noexcept override { return true; }
+ void save() override;
+ void reload() override;
+
+public slots:
+ void doOK();
+ void doCancel();
+ void poll_tracker_info();
+};
diff --git a/tracker-trackhat/dialog.ui b/tracker-trackhat/dialog.ui
new file mode 100644
index 00000000..7c2ea8ee
--- /dev/null
+++ b/tracker-trackhat/dialog.ui
@@ -0,0 +1,602 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>trackhat_dialog</class>
+ <widget class="QDialog" name="trackhat_dialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>365</width>
+ <height>485</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>365</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>TrackHat</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="trackhat-res.qrc">
+ <normaloff>:/images/trackhat-64x64.png</normaloff>:/images/trackhat-64x64.png</iconset>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Camera</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="2">
+ <widget class="QSpinBox" name="gain_label">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Exposure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSlider" name="gain_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>239</number>
+ </property>
+ <property name="pageStep">
+ <number>10</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSlider" name="exposure_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>239</number>
+ </property>
+ <property name="pageStep">
+ <number>10</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QSpinBox" name="exposure_label">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Gain</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>LED</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="led_mode">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>Off</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Constant</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dynamic</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Model</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>10</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Type</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="model_type">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>9</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Min point size</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Max point size</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" alignment="Qt::AlignRight">
+ <widget class="QDoubleSpinBox" name="max_point_size">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="minimum">
+ <double>1.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>999.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" alignment="Qt::AlignRight">
+ <widget class="QDoubleSpinBox" name="min_point_size">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="minimum">
+ <double>1.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>999.000000000000000</double>
+ </property>
+ <property name="value">
+ <double>1.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Tracking</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item row="0" column="2">
+ <widget class="QDoubleSpinBox" name="point_filter_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="decimals">
+ <number>2</number>
+ </property>
+ <property name="maximum">
+ <double>300.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSlider" name="point_filter_limit">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>99</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Limit</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>Deadzone</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="enable_point_filter">
+ <property name="text">
+ <string>Point filter</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QDoubleSpinBox" name="point_filter_limit_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="prefix">
+ <string/>
+ </property>
+ <property name="suffix">
+ <string/>
+ </property>
+ <property name="decimals">
+ <number>2</number>
+ </property>
+ <property name="maximum">
+ <double>1.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSlider" name="point_filter_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>300</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSlider" name="point_filter_deadzone">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QDoubleSpinBox" name="point_filter_deadzone_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="decimals">
+ <number>2</number>
+ </property>
+ <property name="maximum">
+ <double>300.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="status_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Status</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_4">
+ <property name="title">
+ <string>Raw data</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>X</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="label_y">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="label_z">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_13">
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="label_15">
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_11">
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label_x">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_12">
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="label_14">
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="label_yaw">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QLabel" name="label_pitch">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QLabel" name="label_roll">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>10</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>exposure_slider</tabstop>
+ <tabstop>gain_slider</tabstop>
+ <tabstop>led_mode</tabstop>
+ <tabstop>model_type</tabstop>
+ <tabstop>min_point_size</tabstop>
+ <tabstop>max_point_size</tabstop>
+ <tabstop>enable_point_filter</tabstop>
+ <tabstop>point_filter_slider</tabstop>
+ <tabstop>point_filter_limit</tabstop>
+ <tabstop>point_filter_deadzone</tabstop>
+ </tabstops>
+ <resources>
+ <include location="trackhat-res.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/tracker-trackhat/extractor.cpp b/tracker-trackhat/extractor.cpp
new file mode 100644
index 00000000..58b9fd05
--- /dev/null
+++ b/tracker-trackhat/extractor.cpp
@@ -0,0 +1,21 @@
+#include "trackhat.hpp"
+#include <algorithm>
+#include <iterator>
+
+void trackhat_extractor::extract_points(const pt_frame& data,
+ pt_preview&, bool,
+ std::vector<vec2>& points)
+{
+ points.clear();
+ points.reserve(trackhat_camera::point_count);
+ const auto& copy = data.as_const<trackhat_frame>()->points;
+
+ for (const auto& pt : copy)
+ {
+ if (!pt.ok)
+ continue;
+ constexpr int sz = trackhat_camera::sensor_size;
+ auto [ x, y ] = to_screen_pos(pt.x, pt.y, sz, sz);
+ points.push_back({x, y});
+ }
+}
diff --git a/tracker-trackhat/frame.cpp b/tracker-trackhat/frame.cpp
new file mode 100644
index 00000000..0e97ed0e
--- /dev/null
+++ b/tracker-trackhat/frame.cpp
@@ -0,0 +1,128 @@
+#include "trackhat.hpp"
+#include <opencv2/imgproc.hpp>
+#include "compat/math.hpp"
+
+trackhat_preview::trackhat_preview(int w, int h)
+{
+ frame_bgr.create(h, w, CV_8UC3);
+ frame_bgra.create(h, w, CV_8UC4);
+}
+
+void trackhat_preview::set_last_frame(const pt_frame& frame_)
+{
+ center = {-1, -1};
+ points = frame_.as_const<trackhat_frame>()->points;
+}
+
+void trackhat_preview::draw_head_center(pt_pixel_pos_mixin::f x, pt_pixel_pos_mixin::f y)
+{
+ center = {x, y};
+}
+
+QImage trackhat_preview::get_bitmap()
+{
+ frame_bgr.setTo({0});
+
+ draw_points();
+ draw_center();
+
+ cv::cvtColor(frame_bgr, frame_bgra, cv::COLOR_BGR2BGRA);
+
+ return QImage((const unsigned char*) frame_bgra.data,
+ frame_bgra.cols, frame_bgra.rows,
+ (int)frame_bgra.step.p[0],
+ QImage::Format_ARGB32);
+}
+
+void trackhat_preview::draw_center()
+{
+ if (center == numeric_types::vec2(-1, -1))
+ return;
+
+ auto [px_, py_] = to_pixel_pos(center[0], center[1], frame_bgr.cols, frame_bgr.rows);
+ int px = iround(px_), py = iround(py_);
+
+ const f dpi = (f)frame_bgr.cols / f(320);
+ constexpr int len_ = 9;
+ int len = iround(len_ * dpi);
+
+ static const cv::Scalar color(0, 255, 255);
+ cv::line(frame_bgr,
+ cv::Point(px - len, py),
+ cv::Point(px + len, py),
+ color, 1);
+ cv::line(frame_bgr,
+ cv::Point(px, py - len),
+ cv::Point(px, py + len),
+ color, 1);
+}
+
+void trackhat_preview::draw_points()
+{
+ for (const auto& pt : points)
+ {
+ if (pt.brightness == 0)
+ continue;
+
+ constexpr int sz = trackhat_camera::sensor_size;
+ constexpr f scaling_factor = 10;
+ const int x = pt.x * frame_bgr.cols / sz, y = pt.y * frame_bgr.rows / sz;
+ const f dpi = (f)frame_bgr.cols / f(320);
+ const int W = std::max(1, iround(pt.W * frame_bgr.cols * scaling_factor / sz)),
+ H = std::max(1, iround(pt.H * frame_bgr.rows * scaling_factor / sz));
+ const auto point_color = progn(double c = pt.brightness; return cv::Scalar{c, c, c};);
+ const auto outline_color = pt.ok
+ ? cv::Scalar{255, 255, 0}
+ : cv::Scalar{192, 192, 192};
+
+ cv::ellipse(frame_bgr, {x, y}, {W, H},
+ 0, 0, 360, point_color, -1, cv::LINE_AA);
+ cv::ellipse(frame_bgr, {x, y}, {iround(W + 2*dpi), iround(H + 2*dpi)},
+ 0, 0, 360, outline_color, iround(dpi), cv::LINE_AA);
+
+ char buf[16];
+ std::snprintf(buf, sizeof(buf), "%dpx", pt.area);
+ auto text_color = pt.ok
+ ? cv::Scalar(0, 0, 255)
+ : cv::Scalar(160, 160, 160);
+ const int offx = iround(W + 9*dpi), offy = H*3/2;
+
+ cv::putText(frame_bgr, buf, {x+offx, y+offy},
+ cv::FONT_HERSHEY_PLAIN, iround(dpi), text_color,
+ 1);
+ }
+}
+
+void trackhat_frame::init_points(const trackHat_ExtendedPoints_t& points_, double min_size, double max_size)
+{
+ trackHat_ExtendedPoints_t copy = points_;
+ points = {};
+
+ std::sort(std::begin(copy.m_point), std::end(copy.m_point),
+ [](trackHat_ExtendedPoint_t p1, trackHat_ExtendedPoint_t p2) {
+ return p1.m_averageBrightness > p2.m_averageBrightness;
+ });
+
+ unsigned i = 0;
+
+ for (const trackHat_ExtendedPoint_t& pt : copy.m_point)
+ {
+ if (pt.m_averageBrightness == 0)
+ continue;
+
+ point p = {};
+
+ if (pt.m_area >= min_size && pt.m_area <= max_size)
+ p.ok = true;
+ constexpr f c = (f)2941/trackhat_camera::sensor_size;
+
+ p.brightness = pt.m_averageBrightness;
+ p.area = pt.m_area * c;
+ p.W = std::max(1, pt.m_boundryRigth - pt.m_boundryLeft);
+ p.H = std::max(1, pt.m_boundryDown - pt.m_boundryUp);
+ p.x = trackhat_camera::sensor_size-1-pt.m_coordinateX;
+ p.y = pt.m_coordinateY;
+
+ points[i++] = p;
+ }
+}
diff --git a/tracker-trackhat/handle.cpp b/tracker-trackhat/handle.cpp
new file mode 100644
index 00000000..42902e76
--- /dev/null
+++ b/tracker-trackhat/handle.cpp
@@ -0,0 +1,72 @@
+#undef NDEBUG
+#include "trackhat.hpp"
+#include "compat/sleep.hpp"
+#include "compat/timer.hpp"
+#include <cassert>
+
+bool camera_handle::ensure_connected()
+{
+ if (state_ >= st_streaming)
+ return true;
+ else if (state_ == st_stopped)
+ return false;
+
+ Timer t;
+
+ constexpr int max_attempts = 5;
+
+ if (!ensure_device_exists())
+ goto error;
+
+ for (int i = 0; i < max_attempts; i++)
+ {
+ if (!th_check(trackHat_Connect(&device_, TH_FRAME_EXTENDED)))
+ {
+ state_ = st_streaming;
+ if (int ms = (int)t.elapsed_ms(); ms > 1000)
+ qDebug() << "tracker/trackhat: connecting took" << ms << "ms";
+ return true;
+ }
+
+ auto dbg = qDebug();
+ dbg << "tracker/trackhat: connect failed, retry";
+ dbg.space(); dbg.nospace();
+ dbg << (i+1) << "/" << max_attempts;
+ portable::sleep(50);
+ }
+
+error:
+ disconnect();
+ return false;
+}
+
+bool camera_handle::ensure_device_exists()
+{
+ switch (state_)
+ {
+ case st_streaming:
+ return true;
+ case st_detected:
+ disconnect();
+ [[fallthrough]];
+ case st_stopped:
+ assert(!th_check(trackHat_Initialize(&device_)) && device_.m_pInternal);
+ if (auto error = th_check(trackHat_DetectDevice(&device_)); error)
+ {
+ disconnect();
+ return false;
+ }
+ state_ = st_detected;
+ return true;
+ }
+}
+
+void camera_handle::disconnect()
+{
+ state_ = st_stopped;
+ if (device_.m_pInternal)
+ {
+ (void)!th_check(trackHat_Disconnect(&device_));
+ trackHat_Deinitialize(&device_);
+ }
+}
diff --git a/tracker-trackhat/images/trackhat-64x64.png b/tracker-trackhat/images/trackhat-64x64.png
new file mode 100644
index 00000000..9e856c23
--- /dev/null
+++ b/tracker-trackhat/images/trackhat-64x64.png
Binary files differ
diff --git a/tracker-trackhat/images/trackhat.ico b/tracker-trackhat/images/trackhat.ico
new file mode 100644
index 00000000..b5f34db3
--- /dev/null
+++ b/tracker-trackhat/images/trackhat.ico
Binary files differ
diff --git a/tracker-trackhat/images/trackhat.png b/tracker-trackhat/images/trackhat.png
new file mode 100644
index 00000000..4f17de81
--- /dev/null
+++ b/tracker-trackhat/images/trackhat.png
Binary files differ
diff --git a/tracker-trackhat/lang/nl_NL.ts b/tracker-trackhat/lang/nl_NL.ts
new file mode 100644
index 00000000..ebc53a27
--- /dev/null
+++ b/tracker-trackhat/lang/nl_NL.ts
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>trackhat_dialog</name>
+ <message>
+ <source>Cap</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip (left)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip (right)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mini Clip (left)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mini Clip (right)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Custom</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status: Tracking stopped.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status: %1 points detected. Good!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status: %1 points detected. BAD!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>TrackHat</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Exposure</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Min point size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max point size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracking</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Point filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Limit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Raw data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>TextLabel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Gain</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>LED</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Off</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Constant</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dynamic</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>trackhat_module</name>
+ <message>
+ <source>TrackHat v1 Sensor</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-trackhat/lang/ru_RU.ts b/tracker-trackhat/lang/ru_RU.ts
new file mode 100644
index 00000000..49d247be
--- /dev/null
+++ b/tracker-trackhat/lang/ru_RU.ts
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>trackhat_dialog</name>
+ <message>
+ <source>Cap</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip (left)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip (right)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mini Clip (left)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mini Clip (right)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Custom</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status: Tracking stopped.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status: %1 points detected. Good!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status: %1 points detected. BAD!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>TrackHat</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Exposure</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Min point size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max point size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracking</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Point filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Limit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Raw data</source>
+ <translation>ИÑходные данные</translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>TextLabel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Gain</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>LED</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Off</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Constant</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dynamic</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>trackhat_module</name>
+ <message>
+ <source>TrackHat v1 Sensor</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-trackhat/lang/stub.ts b/tracker-trackhat/lang/stub.ts
new file mode 100644
index 00000000..adb23557
--- /dev/null
+++ b/tracker-trackhat/lang/stub.ts
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="stub">
+<context>
+ <name>trackhat_dialog</name>
+ <message>
+ <source>TrackHat</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Exposure</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Min point size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max point size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracking</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Limit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Point filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Raw data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>TextLabel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Cap</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip (left)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip (right)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mini Clip (left)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mini Clip (right)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Custom</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status: Tracking stopped.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status: %1 points detected. Good!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status: %1 points detected. BAD!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Gain</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>LED</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Off</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Constant</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dynamic</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>trackhat_module</name>
+ <message>
+ <source>TrackHat v1 Sensor</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-trackhat/lang/zh_CN.ts b/tracker-trackhat/lang/zh_CN.ts
new file mode 100644
index 00000000..5837eeb1
--- /dev/null
+++ b/tracker-trackhat/lang/zh_CN.ts
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>trackhat_dialog</name>
+ <message>
+ <source>Cap</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip (left)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip (right)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mini Clip (left)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mini Clip (right)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Custom</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status: Tracking stopped.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status: %1 points detected. Good!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status: %1 points detected. BAD!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>TrackHat</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Exposure</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Min point size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max point size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracking</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Point filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Limit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Raw data</source>
+ <translation>跟踪器原始数æ®</translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>TextLabel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation>å航</translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation>横滚</translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation>仰俯</translation>
+ </message>
+ <message>
+ <source>Deadzone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Gain</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>LED</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Off</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Constant</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dynamic</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>trackhat_module</name>
+ <message>
+ <source>TrackHat v1 Sensor</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-trackhat/led.cpp b/tracker-trackhat/led.cpp
new file mode 100644
index 00000000..db5ab6c2
--- /dev/null
+++ b/tracker-trackhat/led.cpp
@@ -0,0 +1,66 @@
+#include "trackhat.hpp"
+
+namespace trackhat_impl {
+
+void led_updater::update_(trackHat_Device_t* device, trackHat_SetLeds_t leds)
+{
+ if (leds_.ledRedState == leds.ledRedState &&
+ leds_.ledGreenState == leds.ledGreenState &&
+ leds_.ledBlueState == leds.ledBlueState)
+ return;
+ (void)trackHat_SetLeds(device, &leds);
+ leds_ = leds;
+}
+
+trackHat_SetLeds_t led_updater::next_state(led_mode mode, led_state new_state)
+{
+ switch (mode)
+ {
+ case led_mode::off:
+ state_ = led_state::stopped;
+ timer_ = std::nullopt;
+ return LED_off;
+ default:
+ case led_mode::constant:
+ state_ = led_state::stopped;
+ timer_ = std::nullopt;
+ return LED_idle;
+ case led_mode::dynamic:
+ break;
+ }
+
+ if (new_state <= led_state::stopped)
+ {
+ state_ = new_state;
+ timer_ = std::nullopt;
+ return LED_idle;
+ }
+ else if (new_state == state_)
+ {
+ timer_ = std::nullopt;
+ return leds_;
+ }
+ else if (!timer_)
+ {
+ timer_ = Timer{};
+ return leds_;
+ }
+ else if (timer_->elapsed_ms() > SWITCH_TIME_MS)
+ {
+ state_ = new_state;
+ timer_ = std::nullopt;
+ return new_state == led_state::not_tracking
+ ? LED_not_tracking
+ : LED_tracking;
+ }
+ else
+ return leds_;
+}
+
+void led_updater::update(trackHat_Device_t* device, led_mode mode, led_state new_state)
+{
+ auto leds = next_state(mode, new_state);
+ update_(device, leds);
+}
+
+} // namespace trackhat_impl
diff --git a/tracker-trackhat/metadata.cpp b/tracker-trackhat/metadata.cpp
new file mode 100644
index 00000000..8c6f4de0
--- /dev/null
+++ b/tracker-trackhat/metadata.cpp
@@ -0,0 +1,39 @@
+#include "metadata.hpp"
+#include "api/plugin-api.hpp"
+#include "trackhat.hpp"
+#include "dialog.hpp"
+
+// XXX TODO
+const QString trackhat_metadata::module_name = QStringLiteral("tracker-trackhat/pt");
+
+pt_runtime_traits::pointer<pt_camera> trackhat_metadata::make_camera() const
+{
+ return std::make_shared<trackhat_camera>();
+}
+
+pt_runtime_traits::pointer<pt_point_extractor> trackhat_metadata::make_point_extractor() const
+{
+ return std::make_shared<trackhat_extractor>();
+}
+
+pt_runtime_traits::pointer<pt_frame> trackhat_metadata::make_frame() const
+{
+ return std::make_shared<trackhat_frame>();
+}
+
+pt_runtime_traits::pointer<pt_preview> trackhat_metadata::make_preview(int w, int h) const
+{
+ return std::make_shared<trackhat_preview>(w, h);
+}
+
+QString trackhat_metadata::get_module_name() const
+{
+ return trackhat_metadata::module_name;
+}
+
+trackhat_pt::trackhat_pt() :
+ Tracker_PT(pt_runtime_traits::pointer<pt_runtime_traits>(new trackhat_metadata))
+{
+}
+
+OPENTRACK_DECLARE_TRACKER(trackhat_pt, trackhat_dialog, trackhat_module)
diff --git a/tracker-trackhat/metadata.hpp b/tracker-trackhat/metadata.hpp
new file mode 100644
index 00000000..f1355064
--- /dev/null
+++ b/tracker-trackhat/metadata.hpp
@@ -0,0 +1,21 @@
+#pragma once
+#include "trackhat.hpp"
+#include "../tracker-pt/ftnoir_tracker_pt.h"
+
+class trackhat_pt final : public Tracker_PT
+{
+ Q_OBJECT
+
+public:
+ trackhat_pt();
+};
+
+class trackhat_module final : public Metadata
+{
+ Q_OBJECT
+
+public:
+ QString name() override { return tr("TrackHat v1 Sensor"); }
+ QIcon icon() override { return QIcon(":/images/trackhat-64x64.png"); }
+ static const QString module_name;
+};
diff --git a/tracker-trackhat/settings.cpp b/tracker-trackhat/settings.cpp
new file mode 100644
index 00000000..dcbf6b37
--- /dev/null
+++ b/tracker-trackhat/settings.cpp
@@ -0,0 +1,144 @@
+#include "trackhat.hpp"
+#include "compat/sleep.hpp"
+#include "compat/timer.hpp"
+
+namespace trackhat_impl {
+
+trackhat_settings::trackhat_settings() : opts{"tracker-trackhat"}
+{
+}
+
+void setting_receiver::settings_changed()
+{
+ changed = true;
+}
+
+bool setting_receiver::test_and_clear()
+{
+ bool x = true;
+ return changed.compare_exchange_strong(x, false);
+}
+
+setting_receiver::setting_receiver(bool value) : changed{value}
+{
+}
+
+} // ns trackhat_impl
+
+void trackhat_camera::set_pt_options()
+{
+ s.min_point_size = t.min_pt_size;
+ s.max_point_size = t.max_pt_size;
+
+ switch (t.model)
+ {
+ default:
+ case model_cap:
+ s.t_MH_x = 0; s.t_MH_y = 0; s.t_MH_z = 0;
+ break;
+ case model_mystery_meat:
+ break;
+ case model_clip_left:
+ case model_mini_clip_left:
+ s.t_MH_x = -135; s.t_MH_y = 0; s.t_MH_z = 0;
+ break;
+ case model_clip_right:
+ case model_mini_clip_right:
+ s.t_MH_x = 135; s.t_MH_y = 0; s.t_MH_z = 0;
+ break;
+ }
+
+ switch (t.model)
+ {
+ default:
+ eval_once(qDebug() << "tracker/trackhat: unknown model");
+ [[fallthrough]];
+ case model_clip_left:
+ case model_clip_right:
+ s.clip_tz = 27; s.clip_ty = 43; s.clip_by = 62; s.clip_bz = 74;
+ s.active_model_panel = 0;
+ break;
+ case model_mini_clip_left:
+ case model_mini_clip_right:
+ s.clip_tz = 13; s.clip_ty = 42; s.clip_by = 60; s.clip_bz = 38;
+ s.active_model_panel = 0;
+ break;
+ case model_cap:
+ s.cap_x = 60; s.cap_y = 90; s.cap_z = 95;
+ s.active_model_panel = 1;
+ break;
+ case model_mystery_meat:
+ break;
+ }
+
+ s.dynamic_pose = t.model == model_cap;
+ s.init_phase_timeout = 500;
+
+ s.camera_name = QStringLiteral("TrackHat Sensor");
+
+ s.enable_point_filter = t.enable_point_filter;
+ s.point_filter_coefficient = *t.point_filter_coefficient;
+ s.point_filter_limit = *t.point_filter_limit;
+ s.point_filter_deadzone = *t.point_filter_deadzone;
+}
+
+bool trackhat_camera::init_regs()
+{
+ auto exp = (uint8_t)t.exposure;
+ auto exp2 = (uint8_t)(exp == 0xff ? 0xf0 : 0xff);
+ auto thres = (uint8_t)0xfe;
+ auto thres2 = (uint8_t)3;
+
+ auto gain = (uint8_t)t.gain;
+ auto gain_c = (uint8_t)(gain/0x10);
+ gain %= 0x10; gain_c %= 4;
+
+ trackHat_SetRegisterGroup_t regs = {
+ {
+ { 0x0c, 0x0f, exp2 }, // exposure lo
+ { 0x0c, 0x10, exp }, // exposure hi
+ { 0x00, 0x0b, 0xff }, // blob area max size
+ { 0x00, 0x0c, 0x03 }, // blob area min size
+ { 0x0c, 0x08, gain }, // gain
+ { 0x0c, 0x0c, gain_c }, // gain multiplier
+ { 0x0c, 0x47, thres }, // min brightness
+ { 0x00, 0x0f, thres2 }, // brightness margin, formula is `thres >= px > thres - fuzz'
+ { 0x0c, 0x60, 0xff }, // scale resolution lo
+ { 0x0c, 0x61, 0x0f }, // scale resolution hi
+ { 0x00, 0x01, 0x01 }, // bank0 sync
+ { 0x01, 0x01, 0x01 }, // bank1 sync
+ },
+ 12,
+ };
+
+ Timer t;
+
+ constexpr int max = 5;
+ int i = 0;
+ for (i = 0; i < max; i++)
+ {
+ TH_ErrorCode status = TH_SUCCESS;
+ status = th_check(trackHat_SetRegisterGroupValue(&*device, &regs));
+ if (status == TH_SUCCESS)
+ break;
+ else if (status != TH_FAILED_TO_SET_REGISTER &&
+ status != TH_ERROR_DEVICE_COMMUNICATION_TIMEOUT)
+ return false;
+ else
+ {
+ auto dbg = qDebug();
+ dbg << "tracker/trackhat: set register retry attempt";
+ dbg.space(); dbg.nospace();
+ dbg << i << "/" << max;
+ portable::sleep(50);
+ }
+ }
+
+ if (i == max)
+ return false;
+
+ if (int elapsed = (int)t.elapsed_ms(); elapsed > 100)
+ qDebug() << "tracker/trackhat: setting registers took" << elapsed << "ms";
+
+ return true;
+}
diff --git a/tracker-trackhat/tracker_trackhat.qrc b/tracker-trackhat/tracker_trackhat.qrc
new file mode 100644
index 00000000..d54010a0
--- /dev/null
+++ b/tracker-trackhat/tracker_trackhat.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>images/trackhat-64x64.png</file>
+ </qresource>
+</RCC>
diff --git a/tracker-trackhat/trackhat-res.qrc b/tracker-trackhat/trackhat-res.qrc
new file mode 100644
index 00000000..9aeb8879
--- /dev/null
+++ b/tracker-trackhat/trackhat-res.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource>
+ <file>images/trackhat-64x64.png</file>
+ </qresource>
+</RCC>
diff --git a/tracker-trackhat/trackhat.hpp b/tracker-trackhat/trackhat.hpp
new file mode 100644
index 00000000..ea00b15e
--- /dev/null
+++ b/tracker-trackhat/trackhat.hpp
@@ -0,0 +1,209 @@
+#pragma once
+
+#include "../tracker-pt/pt-api.hpp"
+#include "compat/timer.hpp"
+#include "options/options.hpp"
+
+#include <track_hat_driver.h>
+
+#include <array>
+#include <atomic>
+#include <optional>
+#include <opencv2/core/mat.hpp>
+
+enum model_type : int
+{
+ model_cap = 1,
+ model_clip_left,
+ model_clip_right,
+ model_mini_clip_left,
+ model_mini_clip_right,
+ model_mystery_meat,
+};
+
+namespace trackhat_impl
+{
+using namespace options;
+
+TH_ErrorCode log_error(TH_ErrorCode error, const char* source, const char* file, int line, const char* function);
+#define th_check_(expr, expr2) ::trackhat_impl::log_error((expr), expr2)
+#define th_check(expr) ::trackhat_impl::log_error((expr), #expr, __FILE__, __LINE__, function_name)
+
+enum class led_mode : unsigned char {
+ off, constant, dynamic,
+};
+
+struct trackhat_settings : opts
+{
+ static constexpr int min_gain = 1, max_gain = 47,
+ min_exposure = 0x10, max_exposure = 0xff;
+ static constexpr int num_exposure_steps = max_gain + max_exposure - min_gain - min_exposure;
+ trackhat_settings();
+ value<slider_value> exposure{b, "exposure", {min_exposure + max_exposure*2/3, min_exposure, max_exposure}};
+ value<slider_value> gain{b, "gain", {min_gain + max_gain*2/3, min_gain, max_gain}};
+ //value<slider_value> threshold{b, "threshold", {0x97, 64, 0xfe}};
+ value<model_type> model{b, "model", model_mini_clip_left};
+ value<double> min_pt_size{b, "min-point-size", 10};
+ value<double> max_pt_size{b, "max-point-size", 50};
+ value<bool> enable_point_filter{b, "enable-point-filter", true };
+ value<slider_value> point_filter_coefficient{b, "point-filter-coefficient", { 1.5, 1, 4 }};
+ value<slider_value> point_filter_limit { b, "point-filter-limit", { 0.1, 0.01, 1 }};
+ value<slider_value> point_filter_deadzone { b, "point-filter-deadzone", {0, 0, 1}};
+ value<led_mode> led { b, "led-mode", led_mode::dynamic };
+};
+
+class setting_receiver : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit setting_receiver(bool value);
+ bool test_and_clear();
+public slots:
+ void settings_changed();
+private:
+ std::atomic<bool> changed{false};
+};
+
+enum class led_state : unsigned char {
+ invalid, stopped, not_tracking, tracking,
+};
+
+struct led_updater final {
+ trackHat_SetLeds_t leds_ {TH_UNCHANGED, TH_UNCHANGED, TH_UNCHANGED};
+ std::optional<Timer> timer_;
+ led_state state_ = led_state::invalid;
+
+ trackHat_SetLeds_t next_state(led_mode mode, led_state new_state);
+ void update_(trackHat_Device_t* device, trackHat_SetLeds_t leds);
+ void update(trackHat_Device_t* device, led_mode mode, led_state new_state);
+
+ static constexpr int SWITCH_TIME_MS = 2000;
+ static constexpr
+ trackHat_SetLeds_t LED_idle = {TH_OFF, TH_SOLID, TH_OFF},
+ LED_off = {TH_OFF, TH_OFF, TH_OFF},
+ LED_tracking {TH_OFF, TH_SOLID, TH_SOLID},
+ LED_not_tracking {TH_SOLID, TH_OFF, TH_OFF};
+};
+
+} // ns trackhat_impl
+
+using trackhat_impl::trackhat_settings;
+using trackhat_impl::led_updater;
+
+struct trackhat_metadata final : pt_runtime_traits
+{
+ pt_runtime_traits::pointer<pt_camera> make_camera() const override;
+ pt_runtime_traits::pointer<pt_point_extractor> make_point_extractor() const override;
+ pt_runtime_traits::pointer<pt_frame> make_frame() const override;
+ pt_runtime_traits::pointer<pt_preview> make_preview(int w, int h) const override;
+ QString get_module_name() const override;
+
+ OTR_DISABLE_MOVE_COPY(trackhat_metadata);
+
+ trackhat_metadata() = default;
+ ~trackhat_metadata() override = default;
+
+ static const QString module_name;
+};
+
+struct point
+{
+ int brightness = 0, area, x, y, W, H;
+ bool ok = false;
+};
+
+struct camera_handle final
+{
+ OTR_DISABLE_MOVE_COPY(camera_handle);
+ trackHat_Device_t* operator->() { return &device_; }
+ trackHat_Device_t& operator*() { return device_; }
+
+ camera_handle() = default;
+ ~camera_handle() = default;
+
+ constexpr operator bool() const { return state_ >= st_streaming; }
+ [[nodiscard]] bool ensure_connected();
+ [[nodiscard]] bool ensure_device_exists();
+ void disconnect();
+private:
+ trackHat_Device_t device_ = {};
+ enum state { st_stopped, st_detected, st_streaming, };
+ state state_ = st_stopped;
+};
+
+struct trackhat_camera final : pt_camera
+{
+ trackhat_camera();
+ ~trackhat_camera() override;
+
+ OTR_DISABLE_MOVE_COPY(trackhat_camera);
+
+ bool start(const pt_settings& s) override;
+ void stop() override;
+
+ pt_camera::result get_frame(pt_frame& frame) override;
+ pt_camera::result get_info() const override;
+ pt_camera_info get_desired() const override;
+
+ QString get_desired_name() const override;
+ QString get_active_name() const override;
+
+ void set_fov(f value) override;
+ void show_camera_settings() override;
+
+ f deadzone_amount() const override { return 10; }
+
+ static constexpr int sensor_size = 4096;
+ static constexpr int sensor_fov = 52;
+ static constexpr int point_count = TRACK_HAT_NUMBER_OF_POINTS;
+ static constexpr bool debug_mode = true;
+
+private:
+ trackhat_impl::setting_receiver sig{true};
+
+ [[nodiscard]] bool init_regs();
+ void set_pt_options();
+
+ camera_handle device;
+ pt_settings s{trackhat_metadata::module_name};
+ trackhat_settings t;
+ led_updater led;
+};
+
+struct trackhat_frame final : pt_frame
+{
+ void init_points(const trackHat_ExtendedPoints_t& points, double min_size, double max_size);
+ trackhat_frame() = default;
+ ~trackhat_frame() override = default;
+
+ std::array<point, trackhat_camera::point_count> points;
+};
+
+struct trackhat_preview final : pt_preview
+{
+ QImage get_bitmap() override;
+ void draw_head_center(f x, f y) override;
+ void set_last_frame(const pt_frame&) override; // NOLINT(misc-unconventional-assign-operator)
+
+ trackhat_preview(int w, int h);
+ ~trackhat_preview() override = default;
+ void draw_points();
+ void draw_center();
+
+ OTR_DISABLE_MOVE_COPY(trackhat_preview);
+
+ cv::Mat frame_bgr, frame_bgra;
+ numeric_types::vec2 center{-1, -1};
+ std::array<point, trackhat_camera::point_count> points;
+};
+
+struct trackhat_extractor final : pt_point_extractor
+{
+ void extract_points(const pt_frame& data, pt_preview&, bool, std::vector<vec2>& points) override;
+
+ OTR_DISABLE_MOVE_COPY(trackhat_extractor);
+
+ trackhat_extractor() = default;
+ ~trackhat_extractor() override = default;
+};
diff --git a/tracker-udp/ftnoir_tracker_udp.cpp b/tracker-udp/ftnoir_tracker_udp.cpp
index 318683f7..f1f9f033 100644
--- a/tracker-udp/ftnoir_tracker_udp.cpp
+++ b/tracker-udp/ftnoir_tracker_udp.cpp
@@ -44,16 +44,20 @@ void udp::run()
}
while (sock.hasPendingDatagrams());
- if (ok &&
- progn(
- for (unsigned i = 0; i < 6; i++)
+ if (ok)
+ {
+ for (unsigned i = 0; i < 6; i++)
+ {
+ int val = std::fpclassify(last_recv_pose2[i]);
+ if (val == FP_NAN || val == FP_INFINITE)
{
- int val = std::fpclassify(last_recv_pose2[i]);
- if (val == FP_NAN || val == FP_INFINITE)
- return false;
+ ok = false;
+ break;
}
- return true;
- ))
+ }
+ }
+
+ if (ok)
{
for (unsigned i = 0; i < 6; i++)
last_recv_pose[i] = last_recv_pose2[i];
@@ -66,7 +70,7 @@ void udp::run()
module_status udp::start_tracker(QFrame*)
{
- if (!sock.bind(QHostAddress::Any, quint16(s.port), QUdpSocket::DontShareAddress))
+ if (!sock.bind(QHostAddress::Any, quint16(s.port), QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint))
return error(tr("Can't bind socket -- %1").arg(sock.errorString()));
sock.moveToThread(this);
@@ -103,4 +107,4 @@ void udp::data(double *data)
}
-OPENTRACK_DECLARE_TRACKER(udp, dialog_udp, udpDll)
+OPENTRACK_DECLARE_TRACKER(udp, dialog_udp, udp_receiver_dll)
diff --git a/tracker-udp/ftnoir_tracker_udp.h b/tracker-udp/ftnoir_tracker_udp.h
index bccb2891..86e82dc6 100644
--- a/tracker-udp/ftnoir_tracker_udp.h
+++ b/tracker-udp/ftnoir_tracker_udp.h
@@ -51,10 +51,11 @@ private slots:
void doCancel();
};
-class udpDll : public Metadata
+class udp_receiver_dll : public Metadata
{
-public:
- QString name() { return otr_tr("UDP over network"); }
+ Q_OBJECT
+
+ QString name() { return tr("UDP over network"); }
QIcon icon() { return QIcon(":/images/opentrack.png"); }
};
diff --git a/tracker-udp/lang/de_DE.ts b/tracker-udp/lang/de_DE.ts
new file mode 100644
index 00000000..a609cd25
--- /dev/null
+++ b/tracker-udp/lang/de_DE.ts
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>UICFTNClientControls</name>
+ <message>
+ <source>UDP tracker settings</source>
+ <translation>UDP-Tracker-Einstellungen</translation>
+ </message>
+ <message>
+ <source>Port</source>
+ <translation>Port</translation>
+ </message>
+ <message>
+ <source>Add to axis</source>
+ <translation>Zur Achse hinzufügen</translation>
+ </message>
+ <message>
+ <source>yaw</source>
+ <translation>Gieren</translation>
+ </message>
+ <message>
+ <source>0</source>
+ <translation>0</translation>
+ </message>
+ <message>
+ <source>+90</source>
+ <translation>+90</translation>
+ </message>
+ <message>
+ <source>-90</source>
+ <translation>-90</translation>
+ </message>
+ <message>
+ <source>+180</source>
+ <translation>+180</translation>
+ </message>
+ <message>
+ <source>-180</source>
+ <translation>-180</translation>
+ </message>
+ <message>
+ <source>pitch</source>
+ <translation>Nicken</translation>
+ </message>
+ <message>
+ <source>roll</source>
+ <translation>Rollen</translation>
+ </message>
+</context>
+<context>
+ <name>udp</name>
+ <message>
+ <source>Can&apos;t bind socket -- %1</source>
+ <translation>Kann nicht an Socket binden -- %1</translation>
+ </message>
+</context>
+<context>
+ <name>udp_receiver_dll</name>
+ <message>
+ <source>UDP over network</source>
+ <translation>UDP über Netzwerk</translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-udp/lang/nl_NL.ts b/tracker-udp/lang/nl_NL.ts
index a7e31195..c15e8f93 100644
--- a/tracker-udp/lang/nl_NL.ts
+++ b/tracker-udp/lang/nl_NL.ts
@@ -55,4 +55,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>udp_receiver_dll</name>
+ <message>
+ <source>UDP over network</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-udp/lang/ru_RU.ts b/tracker-udp/lang/ru_RU.ts
index 5e763652..1b401dc4 100644
--- a/tracker-udp/lang/ru_RU.ts
+++ b/tracker-udp/lang/ru_RU.ts
@@ -55,4 +55,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>udp_receiver_dll</name>
+ <message>
+ <source>UDP over network</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-udp/lang/stub.ts b/tracker-udp/lang/stub.ts
index 6b8cddea..0ebbf95f 100644
--- a/tracker-udp/lang/stub.ts
+++ b/tracker-udp/lang/stub.ts
@@ -55,4 +55,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>udp_receiver_dll</name>
+ <message>
+ <source>UDP over network</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-udp/lang/zh_CN.ts b/tracker-udp/lang/zh_CN.ts
index 6b8cddea..6730c9b8 100644
--- a/tracker-udp/lang/zh_CN.ts
+++ b/tracker-udp/lang/zh_CN.ts
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
<context>
<name>UICFTNClientControls</name>
<message>
@@ -55,4 +55,11 @@
<translation type="unfinished"></translation>
</message>
</context>
+<context>
+ <name>udp_receiver_dll</name>
+ <message>
+ <source>UDP over network</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-wii/CMakeLists.txt b/tracker-wii/CMakeLists.txt
index 76a43a77..fb840385 100644
--- a/tracker-wii/CMakeLists.txt
+++ b/tracker-wii/CMakeLists.txt
@@ -1,9 +1,16 @@
if(WIN32)
- find_package(OpenCV 3.0 QUIET)
+ include(opentrack-opencv)
+ find_package(OpenCV QUIET)
if(OpenCV_FOUND)
+ foreach(k core imgproc)
+ otr_install_lib("opencv_${k}" "${opentrack-libexec}")
+ endforeach()
add_subdirectory(wiiyourself)
otr_module(tracker-wii)
- target_link_libraries(opentrack-tracker-wii opentrack-tracker-pt-base opentrack-wiiyourself bthprops)
- target_include_directories(opentrack-tracker-wii PRIVATE "${CMAKE_SOURCE_DIR}/tracker-pt")
+ target_link_libraries(${self} opencv_imgproc opentrack-tracker-pt-base opentrack-wiiyourself bthprops)
+ if(MINGW32)
+ target_link_libraries(${self} hid)
+ endif()
+ target_include_directories(${self} PRIVATE "${CMAKE_SOURCE_DIR}/tracker-pt")
endif()
endif()
diff --git a/tracker-wii/lang/nl_NL.ts b/tracker-wii/lang/nl_NL.ts
index 9e739505..4c21a820 100644
--- a/tracker-wii/lang/nl_NL.ts
+++ b/tracker-wii/lang/nl_NL.ts
@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="nl_NL">
+<context>
+ <name>wii_metadata_pt</name>
+ <message>
+ <source>WiiPointTracker 1.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-wii/lang/ru_RU.ts b/tracker-wii/lang/ru_RU.ts
index f62cf2e1..ff0e7092 100644
--- a/tracker-wii/lang/ru_RU.ts
+++ b/tracker-wii/lang/ru_RU.ts
@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
+<context>
+ <name>wii_metadata_pt</name>
+ <message>
+ <source>WiiPointTracker 1.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-wii/lang/stub.ts b/tracker-wii/lang/stub.ts
index 6401616d..d67c57ad 100644
--- a/tracker-wii/lang/stub.ts
+++ b/tracker-wii/lang/stub.ts
@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
+<context>
+ <name>wii_metadata_pt</name>
+ <message>
+ <source>WiiPointTracker 1.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-wii/lang/zh_CN.ts b/tracker-wii/lang/zh_CN.ts
index 6401616d..6959d95b 100644
--- a/tracker-wii/lang/zh_CN.ts
+++ b/tracker-wii/lang/zh_CN.ts
@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>wii_metadata_pt</name>
+ <message>
+ <source>WiiPointTracker 1.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
</TS>
diff --git a/tracker-wii/wii_camera.cpp b/tracker-wii/wii_camera.cpp
index af9a2d1f..bec47651 100644
--- a/tracker-wii/wii_camera.cpp
+++ b/tracker-wii/wii_camera.cpp
@@ -15,14 +15,9 @@
#include "wii_camera.h"
#include "wii_frame.hpp"
-#include "compat/sleep.hpp"
-#include "compat/camera-names.hpp"
#include "compat/math-imports.hpp"
#include <opencv2/imgproc.hpp>
-
-#include "cv/video-property-page.hpp"
-
#include <bluetoothapis.h>
using namespace pt_module;
@@ -33,93 +28,111 @@ WIICamera::WIICamera(const QString& module_name) : s { module_name }
cam_info.res_x = 1024;
cam_info.res_y = 768;
cam_info.fov = 42.0f;
- cam_info.idx = 0;
+ cam_info.name = "Wii";
}
WIICamera::~WIICamera()
{
- //stop();
+ stop();
}
QString WIICamera::get_desired_name() const
{
- return desired_name;
+ return QStringLiteral("Wii");
}
QString WIICamera::get_active_name() const
{
- return active_name;
+ return get_desired_name();
}
void WIICamera::show_camera_settings()
{
-
}
WIICamera::result WIICamera::get_info() const
{
- if (cam_info.res_x == 0 || cam_info.res_y == 0)
- return result(false, pt_camera_info());
- return result(true, cam_info);
+ if (cam_info.res_x == 0 || cam_info.res_y == 0)
+ return result(false, pt_camera_info());
+ return result(true, cam_info);
}
WIICamera::result WIICamera::get_frame(pt_frame& frame_)
{
- cv::Mat& frame = frame_.as<WIIFrame>()->mat;
+ cv::Mat& frame = frame_.as<WIIFrame>()->mat;
struct wii_info& wii = frame_.as<WIIFrame>()->wii;
+ const wii_camera_status new_frame = get_frame(frame);
- const wii_camera_status new_frame = _get_frame(frame);
//create a fake blank frame
- frame = cv::Mat(cam_info.res_x, cam_info.res_y, CV_8UC3, cv::Scalar(0, 0, 0));
+ frame.create(cam_info.res_x, cam_info.res_y, CV_8UC3);
+ frame.setTo({0});
wii.status = new_frame;
- switch (new_frame)
- {
+ switch (new_frame)
+ {
case wii_cam_data_change:
- _get_status(wii);
- _get_points(wii);
+ get_status(wii);
+ get_points(wii);
break;
case wii_cam_data_no_change:
return result(false, cam_info);
+ default:
+ break;
}
return result(true, cam_info);
}
-bool WIICamera::start(int idx, int fps, int res_x, int res_y)
+bool WIICamera::start(const pt_settings&)
{
+ if (m_pDev)
+ return true;
m_pDev = std::make_unique<wiimote>();
m_pDev->ChangedCallback = on_state_change;
m_pDev->CallbackTriggerFlags = (state_change_flags)(CONNECTED |
EXTENSION_CHANGED |
MOTIONPLUS_CHANGED);
- return true;
+ return true;
}
void WIICamera::stop()
{
- onExit = true;
- m_pDev->ChangedCallback = NULL;
- m_pDev->Disconnect();
- Beep(1000, 200);
- if (m_pDev) {
- m_pDev=nullptr;
- m_pDev = NULL;
- }
+ if (!m_pDev)
+ return;
- desired_name = QString();
- active_name = QString();
- cam_info = pt_camera_info();
- cam_desired = pt_camera_info();
+ cam_info = {};
+ cam_desired = {};
+ pitch_ = 0; roll_ = 0;
+
+ m_pDev->ChangedCallback = nullptr;
+ m_pDev->Disconnect();
+ m_pDev = nullptr;
}
+#ifdef __MINGW32__
+extern "C"
+ DWORD WINAPI BluetoothAuthenticateDevice(
+ HWND hwndParent,
+ HANDLE hRadio,
+ BLUETOOTH_DEVICE_INFO *pbtbi,
+ PWSTR pszPasskey,
+ ULONG ulPasskeyLength
+ );
+
+#ifndef BLUETOOTH_SERVICE_ENABLE
+# define BLUETOOTH_SERVICE_DISABLE 0x00
+# define BLUETOOTH_SERVICE_ENABLE 0x01
+#endif
+
+#endif
-wii_camera_status WIICamera::_pair()
+wii_camera_status WIICamera::pair()
{
wii_camera_status ret = wii_cam_wait_for_sync;
HBLUETOOTH_RADIO_FIND hbt;
BLUETOOTH_FIND_RADIO_PARAMS bt_param;
- HANDLE hbtlist[10];
+ constexpr int max_devices = 64;
+ HANDLE hbtlist[max_devices];
int ibtidx = 0;
bool wiifound = false;
@@ -127,12 +140,10 @@ wii_camera_status WIICamera::_pair()
hbt = BluetoothFindFirstRadio(&bt_param, hbtlist + ibtidx);
if (!hbt) { ret = wii_cam_wait_for_dongle; return ret; }
do
- {
ibtidx++;
- } while (BluetoothFindNextRadio(&bt_param, hbtlist + ibtidx));
+ while (ibtidx < max_devices && BluetoothFindNextRadio(&bt_param, hbtlist + ibtidx));
BluetoothFindRadioClose(hbt);
-
int i;
bool error = false;
for (i = 0; i < ibtidx; i++)
@@ -143,11 +154,12 @@ wii_camera_status WIICamera::_pair()
if (ERROR_SUCCESS != BluetoothGetRadioInfo(hbtlist[i], &btinfo)) {break;}
HBLUETOOTH_DEVICE_FIND hbtdevfd;
- BLUETOOTH_DEVICE_SEARCH_PARAMS btdevparam;
+ BLUETOOTH_DEVICE_SEARCH_PARAMS btdevparam {};
BLUETOOTH_DEVICE_INFO btdevinfo;
btdevinfo.dwSize = sizeof(btdevinfo);
btdevparam.dwSize = sizeof(btdevparam);
+ btdevparam.fReturnUnknown = TRUE;
btdevparam.fReturnAuthenticated = TRUE;
btdevparam.fReturnConnected = TRUE;
btdevparam.fReturnRemembered = TRUE;
@@ -162,7 +174,8 @@ wii_camera_status WIICamera::_pair()
}
do
{
- if (wcscmp(btdevinfo.szName, L"Nintendo RVL-CNT-01-TR") && wcscmp(btdevinfo.szName, L"Nintendo RVL-CNT-01"))
+ if (!!wcscmp(btdevinfo.szName, L"Nintendo RVL-CNT-01-TR") &&
+ !!wcscmp(btdevinfo.szName, L"Nintendo RVL-CNT-01"))
{
continue;
}
@@ -183,11 +196,11 @@ wii_camera_status WIICamera::_pair()
pwd[4] = btinfo.address.rgBytes[4];
pwd[5] = btinfo.address.rgBytes[5];
- if (ERROR_SUCCESS != BluetoothAuthenticateDevice(NULL, hbtlist[i], &btdevinfo, pwd, 6)) { error = true; continue; }
+ if (ERROR_SUCCESS != BluetoothAuthenticateDevice(nullptr, hbtlist[i], &btdevinfo, pwd, 6)) { error = true; continue; }
DWORD servicecount = 32;
GUID guids[32];
if (ERROR_SUCCESS != BluetoothEnumerateInstalledServices(hbtlist[i], &btdevinfo, &servicecount, guids)) { error = true; continue; }
- if (ERROR_SUCCESS != BluetoothSetServiceState(hbtlist[i], &btdevinfo, &HumanInterfaceDeviceServiceClass_UUID, BLUETOOTH_SERVICE_ENABLE)) { error = true; continue; }
+ if (ERROR_SUCCESS != BluetoothSetServiceState(hbtlist[i], &btdevinfo, (GUID*)&HumanInterfaceDeviceServiceClass_UUID, BLUETOOTH_SERVICE_ENABLE)) { error = true; continue; }
break;
} while (BluetoothFindNextDevice(hbtdevfd, &btdevinfo));
BluetoothFindDeviceClose(hbtdevfd);
@@ -201,19 +214,22 @@ wii_camera_status WIICamera::_pair()
return ret;
}
-wii_camera_status WIICamera::_get_frame(cv::Mat& frame)
+wii_camera_status WIICamera::get_frame(cv::Mat&)
{
wii_camera_status ret = wii_cam_wait_for_connect;
if (!m_pDev->IsConnected()) {
qDebug() << "wii wait";
- ret = _pair();
- switch(ret){
+ ret = pair();
+ switch(ret)
+ {
case wii_cam_wait_for_sync:
m_pDev->Disconnect();
goto goodbye;
case wii_cam_wait_for_connect:
break;
+ default:
+ break;
}
if (!m_pDev->Connect(wiimote::FIRST_AVAILABLE)) {
Beep(500, 30); Sleep(1000);
@@ -238,7 +254,7 @@ goodbye:
return ret;
}
-bool WIICamera::_get_points(struct wii_info& wii)
+bool WIICamera::get_points(struct wii_info& wii)
{
bool dot_sizes = (m_pDev->IR.Mode == wiimote_state::ir::EXTENDED);
bool ret = false;
@@ -269,23 +285,21 @@ bool WIICamera::_get_points(struct wii_info& wii)
return ret;
}
-void WIICamera::_get_status(struct wii_info& wii)
+void WIICamera::get_status(struct wii_info& wii)
{
//draw battery status
wii.BatteryPercent = m_pDev->BatteryPercent;
wii.bBatteryDrained = m_pDev->bBatteryDrained;
//draw horizon
- static int p = 0;
- static int r = 0;
if (m_pDev->Nunchuk.Acceleration.Orientation.UpdateAge < 10)
{
- p = m_pDev->Acceleration.Orientation.Pitch;
- r = m_pDev->Acceleration.Orientation.Roll;
+ pitch_ = (int)m_pDev->Acceleration.Orientation.Pitch;
+ roll_ = (int)m_pDev->Acceleration.Orientation.Roll;
}
- wii.Pitch = p;
- wii.Roll = r;
+ wii.Pitch = pitch_;
+ wii.Roll = roll_;
}
void WIICamera::on_state_change(wiimote &remote,
diff --git a/tracker-wii/wii_camera.h b/tracker-wii/wii_camera.h
index 55def206..3a7993aa 100644
--- a/tracker-wii/wii_camera.h
+++ b/tracker-wii/wii_camera.h
@@ -17,7 +17,6 @@
#include <tuple>
#include <opencv2/core.hpp>
-#include <opencv2/videoio.hpp>
#include <QString>
@@ -28,46 +27,39 @@ namespace pt_module {
struct WIICamera final : pt_camera
{
- WIICamera(const QString& module_name);
- ~WIICamera() override;
+ WIICamera(const QString& module_name);
+ ~WIICamera() override;
- bool start(int idx, int fps, int res_x, int res_y) override;
- void stop() override;
+ bool start(const pt_settings&) override;
+ void stop() override;
- result get_frame(pt_frame& Frame) override;
- result get_info() const override;
+ result get_frame(pt_frame& Frame) override;
+ result get_info() const override;
- pt_camera_info get_desired() const override { return cam_desired; }
- QString get_desired_name() const override;
- QString get_active_name() const override;
+ pt_camera_info get_desired() const override { return cam_desired; }
+ QString get_desired_name() const override;
+ QString get_active_name() const override;
- void set_fov(double value) override {}
- void show_camera_settings() override;
+ void set_fov(f x) override { (void) x; }
+ void show_camera_settings() override;
private:
std::unique_ptr<wiimote> m_pDev;
static void on_state_change(wiimote &remote,
state_change_flags changed,
const wiimote_state &new_state);
- bool onExit = false;
- pt_frame internalframe;
-
- wii_camera_status _pair();
- wii_camera_status _get_frame(cv::Mat& Frame);
- bool _get_points(struct wii_info&);
- void _get_status(struct wii_info&);
- double dt_mean = 0;
+ wii_camera_status pair();
+ wii_camera_status get_frame(cv::Mat& Frame);
+ bool get_points(struct wii_info& wii);
+ void get_status(struct wii_info& wii);
- Timer t;
+ pt_camera_info cam_info;
+ pt_camera_info cam_desired;
+ int pitch_ = 0, roll_ = 0;
+ pt_settings s;
- pt_camera_info cam_info;
- pt_camera_info cam_desired;
- QString desired_name, active_name;
-
- pt_settings s;
-
- static constexpr inline double dt_eps = 1./384;
+ static constexpr inline double dt_eps = 1./384;
};
} // ns pt_module
diff --git a/tracker-wii/wii_frame.cpp b/tracker-wii/wii_frame.cpp
index 9332a704..fcde5235 100644
--- a/tracker-wii/wii_frame.cpp
+++ b/tracker-wii/wii_frame.cpp
@@ -17,34 +17,29 @@
using namespace pt_module;
-WIIPreview& WIIPreview::operator=(const pt_frame& frame_)
+void WIIPreview::set_last_frame(const pt_frame& frame_)
{
const struct wii_info& wii = frame_.as_const<WIIFrame>()->wii;
const cv::Mat& frame = frame_.as_const<const WIIFrame>()->mat;
- ensure_size(frame_copy, frame_out.cols, frame_out.rows, CV_8UC3);
status = wii.status;
if (frame.channels() != 3)
- {
- once_only(qDebug() << "tracker/pt: camera frame depth: 3 !=" << frame.channels());
- return *this;
- }
+ eval_once(qDebug() << "tracker/pt: camera frame depth: 3 !=" << frame.channels());
const bool need_resize = frame.cols != frame_out.cols || frame.rows != frame_out.rows;
if (need_resize)
cv::resize(frame, frame_copy, cv::Size(frame_out.cols, frame_out.rows), 0, 0, cv::INTER_NEAREST);
else
frame.copyTo(frame_copy);
-
- return *this;
}
WIIPreview::WIIPreview(int w, int h)
{
ensure_size(frame_out, w, h, CV_8UC4);
+ ensure_size(frame_copy, w, h, CV_8UC3);
- frame_out.setTo(cv::Scalar(0, 0, 0, 0));
+ frame_copy.setTo(cv::Scalar(0, 0, 0));
}
QImage WIIPreview::get_bitmap()
@@ -61,7 +56,7 @@ QImage WIIPreview::get_bitmap()
if (stride < 64 || stride < frame_out.cols * 4)
{
- once_only(qDebug() << "bad stride" << stride
+ eval_once(qDebug() << "bad stride" << stride
<< "for bitmap size" << frame_copy.cols << frame_copy.rows);
return QImage();
}
@@ -74,7 +69,7 @@ QImage WIIPreview::get_bitmap()
QImage::Format_ARGB32);
}
-void WIIPreview::draw_head_center(double x, double y)
+void WIIPreview::draw_head_center(f x, f y)
{
double px_, py_;
@@ -97,6 +92,6 @@ void WIIPreview::draw_head_center(double x, double y)
void WIIPreview::ensure_size(cv::Mat& frame, int w, int h, int type)
{
- if (frame.cols != w || frame.rows != h)
+ if (frame.cols != w || frame.rows != h || frame.type() != type)
frame = cv::Mat(h, w, type);
}
diff --git a/tracker-wii/wii_frame.hpp b/tracker-wii/wii_frame.hpp
index 31967d10..bbb0c469 100644
--- a/tracker-wii/wii_frame.hpp
+++ b/tracker-wii/wii_frame.hpp
@@ -47,9 +47,9 @@ struct WIIPreview final : pt_preview
{
WIIPreview(int w, int h);
- WIIPreview& operator=(const pt_frame& frame) override;
+ void set_last_frame(const pt_frame& frame) override;
QImage get_bitmap() override;
- void draw_head_center(double x, double y) override;
+ void draw_head_center(f x, f y) override;
operator cv::Mat&() { return frame_copy; }
operator cv::Mat const&() const { return frame_copy; }
@@ -57,9 +57,8 @@ struct WIIPreview final : pt_preview
private:
static void ensure_size(cv::Mat& frame, int w, int h, int type);
- bool fresh = true;
- cv::Mat frame_copy, frame_color, frame_resize, frame_out;
- wii_camera_status status;
+ cv::Mat frame_copy, frame_out;
+ wii_camera_status status = wii_cam_wait_for_connect;
};
} // ns pt_module
diff --git a/tracker-wii/wii_module.cpp b/tracker-wii/wii_module.cpp
index 5ef75f57..0358004f 100644
--- a/tracker-wii/wii_module.cpp
+++ b/tracker-wii/wii_module.cpp
@@ -62,9 +62,9 @@ struct wii_dialog_pt : TrackerDialog_PT
wii_dialog_pt();
};
-class wii_metadata_pt : public Metadata
+struct wii_metadata_pt : Metadata
{
- QString name() { return _("WiiPointTracker 1.1"); }
+ QString name() { return tr("WiiPointTracker 1.1"); }
QIcon icon() { return QIcon(":/Resources/wii.png"); }
};
@@ -76,7 +76,8 @@ using namespace pt_module;
wii_dialog_pt::wii_dialog_pt() : TrackerDialog_PT(module_name)
{
- ui.tabWidget->removeTab(0);
+ ui.camera_settings_groupbox->hide();
+ ui.groupBox_2->hide();
}
OPENTRACK_DECLARE_TRACKER(wii_tracker_pt, wii_dialog_pt, wii_metadata_pt)
diff --git a/tracker-wii/wii_module.hpp b/tracker-wii/wii_module.hpp
new file mode 100644
index 00000000..b167e150
--- /dev/null
+++ b/tracker-wii/wii_module.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "api/plugin-api.hpp"
+
+class wii_metadata_pt : public Metadata
+{
+ Q_OBJECT
+
+ QString name() override;
+ QIcon icon() override;
+};
diff --git a/tracker-wii/wii_point_extractor.cpp b/tracker-wii/wii_point_extractor.cpp
index 215d50b8..4f1f92b9 100644
--- a/tracker-wii/wii_point_extractor.cpp
+++ b/tracker-wii/wii_point_extractor.cpp
@@ -14,15 +14,6 @@
#include "cv/numeric.hpp"
#include "compat/math.hpp"
-#include <opencv2/videoio.hpp>
-
-#undef PREVIEW
-//#define PREVIEW
-
-#if defined PREVIEW
-# include <opencv2/highgui.hpp>
-#endif
-
#include <cmath>
#include <algorithm>
#include <cinttypes>
@@ -30,45 +21,41 @@
#include <QDebug>
-using namespace types;
+using namespace numeric_types;
using namespace pt_module;
WIIPointExtractor::WIIPointExtractor(const QString& module_name) : s(module_name)
{
-
}
//define a temp draw function
-void WIIPointExtractor::_draw_point(cv::Mat& preview_frame, const vec2& p, const cv::Scalar& color, int thinkness)
+void WIIPointExtractor::draw_point(cv::Mat& preview_frame, const vec2& p, const cv::Scalar& color, int thickness)
{
static constexpr int len = 9;
- cv::Point p2(iround(p[0] * preview_frame.cols + preview_frame.cols / 2),
- iround(-p[1] * preview_frame.cols + preview_frame.rows / 2));
+ cv::Point p2(iround(p[0] * preview_frame.cols + preview_frame.cols / 2.f),
+ iround(-p[1] * preview_frame.cols + preview_frame.rows / 2.f));
cv::line(preview_frame,
cv::Point(p2.x - len, p2.y),
cv::Point(p2.x + len, p2.y),
color,
- thinkness);
+ thickness);
cv::line(preview_frame,
cv::Point(p2.x, p2.y - len),
cv::Point(p2.x, p2.y + len),
color,
- thinkness);
-};
+ thickness);
+}
-bool WIIPointExtractor::_draw_points(cv::Mat& preview_frame, const struct wii_info &wii, std::vector<vec2>& points)
+void WIIPointExtractor::draw_points(cv::Mat& preview_frame, const struct wii_info& wii)
{
- const float W = 1024.0f;
- const float H = 768.0f;
- points.reserve(4);
- points.clear();
+ constexpr int W = 1024;
+ constexpr int H = 768;
- for (unsigned index = 0; index < 4; index++)
+ for (const wii_info_points& dot : wii.Points)
{
- const struct wii_info_points &dot = wii.Points[index];
if (dot.bvis) {
//qDebug() << "wii:" << dot.RawX << "+" << dot.RawY;
//anti-clockwise rotate the 2D point
@@ -79,17 +66,12 @@ bool WIIPointExtractor::_draw_points(cv::Mat& preview_frame, const struct wii_in
//vec2 dt((2.0f*RX - W) / W, -(2.0f*RY - H ) / W);
vec2 dt;
std::tie(dt[0], dt[1]) = to_screen_pos(RX, RY, W, H);
-
- points.push_back(dt);
- _draw_point(preview_frame, dt, cv::Scalar(0, 255, 0), dot.isize);
+ draw_point(preview_frame, dt, cv::Scalar(0, 255, 0), std::clamp(dot.isize, 1, 32));
}
}
- const bool success = points.size() >= PointModel::N_POINTS;
-
- return success;
}
-void WIIPointExtractor::_draw_bg(cv::Mat& preview_frame, const struct wii_info &wii)
+void WIIPointExtractor::draw_bg(cv::Mat& preview_frame, const struct wii_info& wii)
{
//draw battery status
cv::line(preview_frame,
@@ -99,8 +81,8 @@ void WIIPointExtractor::_draw_bg(cv::Mat& preview_frame, const struct wii_info &
2);
//draw horizon
- int pdelta = iround((preview_frame.rows / 4) * tan((wii.Pitch)* M_PI / 180.0f));
- int rdelta = iround((preview_frame.cols / 4) * tan((wii.Roll)* M_PI / 180.0f));
+ int pdelta = iround(preview_frame.rows / 4.f * tan(wii.Pitch * pi / 180.f));
+ int rdelta = iround(preview_frame.cols / 4.f * tan(wii.Roll* pi / 180.f));
cv::line(preview_frame,
cv::Point(0, preview_frame.rows / 2 + rdelta - pdelta),
@@ -109,16 +91,46 @@ void WIIPointExtractor::_draw_bg(cv::Mat& preview_frame, const struct wii_info &
1);
}
-void WIIPointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_frame_, std::vector<vec2>& points)
+void WIIPointExtractor::extract_points(const pt_frame& frame_,
+ pt_preview& preview_frame_,
+ bool preview_visible,
+ std::vector<vec2>& points)
{
const struct wii_info& wii = frame_.as_const<WIIFrame>()->wii;
cv::Mat& preview_frame = *preview_frame_.as<WIIPreview>();
- switch (wii.status) {
- case wii_cam_data_change:
- _draw_bg(preview_frame, wii);
- _draw_points(preview_frame, wii, points);
- break;
+ map_points(wii, points);
+ if (preview_visible && wii.status == wii_cam_data_change)
+ {
+ draw_bg(preview_frame, wii);
+ draw_points(preview_frame, wii);
}
}
+bool WIIPointExtractor::map_points(const struct wii_info& wii, std::vector<vec2>& points)
+{
+ constexpr int W = 1024;
+ constexpr int H = 768;
+ points.reserve(4);
+ points.clear();
+
+ for (unsigned index = 0; index < 4; index++) // NOLINT(modernize-loop-convert)
+ {
+ const struct wii_info_points &dot = wii.Points[index];
+ if (dot.bvis) {
+ //qDebug() << "wii:" << dot.RawX << "+" << dot.RawY;
+ //anti-clockwise rotate the 2D point
+ const float RX = W - dot.ux;
+ const float RY = H - dot.uy;
+ //vec2 dt((dot.RawX - W / 2.0f) / W, -(dot.RawY - H / 2.0f) / W);
+ //vec2 dt((RX - W / 2.0f) / W, -(RY - H / 2.0f) / W);
+ //vec2 dt((2.0f*RX - W) / W, -(2.0f*RY - H ) / W);
+ vec2 dt;
+ std::tie(dt[0], dt[1]) = to_screen_pos(RX, RY, W, H);
+
+ points.push_back(dt);
+ }
+ }
+
+ return points.size() >= PointModel::N_POINTS;
+}
diff --git a/tracker-wii/wii_point_extractor.h b/tracker-wii/wii_point_extractor.h
index be0e5f45..b8b25b1a 100644
--- a/tracker-wii/wii_point_extractor.h
+++ b/tracker-wii/wii_point_extractor.h
@@ -16,22 +16,25 @@
namespace pt_module {
-using namespace types;
+using namespace numeric_types;
class WIIPointExtractor final : public pt_point_extractor
{
public:
// extracts points from frame and draws some processing info into frame, if draw_output is set
// dt: time since last call in seconds
- void extract_points(const pt_frame& frame, pt_preview& preview_frame, std::vector<vec2>& points) override;
+ void extract_points(const pt_frame& frame,
+ pt_preview& preview_frame,
+ bool preview_visible,
+ std::vector<vec2>& points) override;
WIIPointExtractor(const QString& module_name);
-private:
- static constexpr int max_blobs = 16;
+private:
pt_settings s;
- void _draw_point(cv::Mat& preview_frame, const vec2& p, const cv::Scalar& color, int thinkness = 1);
- bool _draw_points(cv::Mat& preview_frame, const struct wii_info &wii, std::vector<vec2>& points);
- void _draw_bg(cv::Mat& preview_frame, const struct wii_info &wii);
+ static void draw_point(cv::Mat& preview_frame, const vec2& p, const cv::Scalar& color, int thickness = 1);
+ static void draw_points(cv::Mat& preview_frame, const struct wii_info& wii);
+ static void draw_bg(cv::Mat& preview_frame, const struct wii_info& wii);
+ static bool map_points(const struct wii_info& wii, std::vector<vec2>& points);
};
} // ns impl
diff --git a/tracker-wii/wiiyourself/CMakeLists.txt b/tracker-wii/wiiyourself/CMakeLists.txt
index 6a32fde4..d32c6eb6 100644
--- a/tracker-wii/wiiyourself/CMakeLists.txt
+++ b/tracker-wii/wiiyourself/CMakeLists.txt
@@ -1 +1,2 @@
-otr_module(wiiyourself STATIC)
+otr_module(wiiyourself STATIC NO-QT NO-COMPAT)
+target_link_libraries(${self} PUBLIC setupapi hid winmm)
diff --git a/tracker-wii/wiiyourself/lang/zh_CN.ts b/tracker-wii/wiiyourself/lang/zh_CN.ts
index 6401616d..e5ca8aa9 100644
--- a/tracker-wii/wiiyourself/lang/zh_CN.ts
+++ b/tracker-wii/wiiyourself/lang/zh_CN.ts
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
</TS>
diff --git a/tracker-wii/wiiyourself/wiimote.cpp b/tracker-wii/wiiyourself/wiimote.cpp
index e1e49101..01c80f37 100644
--- a/tracker-wii/wiiyourself/wiimote.cpp
+++ b/tracker-wii/wiiyourself/wiimote.cpp
@@ -8,106 +8,70 @@
//
// wiimote.cpp (tab = 4 spaces)
-// VC-specifics:
-#ifdef _MSC_VER
- // disable warning "C++ exception handler used, but unwind semantics are not enabled."
- // in <xstring> (I don't use it - or just enable C++ exceptions)
-# pragma warning(disable: 4530)
-// auto-link with the necessary libs
-# pragma comment(lib, "setupapi.lib")
-# pragma comment(lib, "hid.lib") // for HID API (from DDK)
-# pragma comment(lib, "winmm.lib") // for timeGetTime()
-#endif // _MSC_VER
-
#include "wiimote.h"
+#include <cmath>
+#include <algorithm> // std::min
+#include <iterator> // std::size
+#include <tchar.h>
#include <setupapi.h>
extern "C" {
-# ifdef __MINGW32__
-# include <ddk/hidsdi.h>// from WinDDK
-# else
-# include <hidsdi.h>
-# endif
+#include <hidsdi.h>
}
#include <sys/types.h> // for _stat
#include <sys/stat.h> // "
#include <process.h> // for _beginthreadex()
-#ifdef __BORLANDC__
-# include <cmath.h> // for orientation
-#else
-# include <math.h> // "
-#endif
#include <mmreg.h> // for WAVEFORMATEXTENSIBLE
#include <mmsystem.h> // for timeGetTime()
-// apparently not defined in some compilers:
-#ifndef min
-# define min(a,b) (((a) < (b)) ? (a) : (b))
-#endif
// ------------------------------------------------------------------------------------
// helpers
// ------------------------------------------------------------------------------------
template<class T> inline T sign (const T& val) { return (val<0)? T(-1) : T(1); }
template<class T> inline T square(const T& val) { return val*val; }
-#define ARRAY_ENTRIES(array) (sizeof(array)/sizeof(array[0]))
// ------------------------------------------------------------------------------------
// Tracing & Debugging
// ------------------------------------------------------------------------------------
-#define PREFIX _T("WiiYourself! : ")
-
-// comment these to auto-strip their code from the library:
-// (they currently use OutputDebugString() via _TRACE() - change to suit)
-#if (_MSC_VER >= 1400) // VC 2005+ (earlier versions don't support variable args)
-# define TRACE(fmt, ...) _TRACE(PREFIX fmt _T("\n"), __VA_ARGS__)
-# define WARN(fmt, ...) _TRACE(PREFIX _T("* ") fmt _T(" *") _T("\n"), __VA_ARGS__)
-#elif defined(__MINGW32__)
-# define TRACE(fmt, ...) _TRACE(PREFIX fmt _T("\n") , ##__VA_ARGS__)
-# define WARN(fmt, ...) _TRACE(PREFIX _T("* ") fmt _T(" *") _T("\n") , ##__VA_ARGS__)
-#endif
-// uncomment any of these for deeper debugging:
-//#define DEEP_TRACE(fmt, ...) _TRACE(PREFIX _T("|") fmt _T("\n"), __VA_ARGS__) // VC 2005+
-//#define DEEP_TRACE(fmt, ...) _TRACE(PREFIX _T("|") fmt _T("\n") , ##__VA_ARGS__) // mingw
-//#define BEEP_DEBUG_READS
-//#define BEEP_DEBUG_WRITES
-//#define BEEP_ON_ORIENTATION_ESTIMATE
-//#define BEEP_ON_PERIODIC_STATUSREFRESH
-// internals: auto-strip code from the macros if they weren't defined
+static_assert(sizeof(TCHAR) == sizeof(char));
+
+template<typename... xs>
+[[maybe_unused]]
+static void trace_ (const char* fmt, const xs&... args)
+{
+ fprintf(stderr, fmt, args...);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+
+template<typename... xs>
+[[maybe_unused]]
+static inline void disabled_trace_(const char*, const xs&...) {}
+
+#define TRACE trace_
+#define WARN trace_
+
#ifndef TRACE
-# define TRACE
+# define TRACE disabled_trace_
#endif
#ifndef DEEP_TRACE
-# define DEEP_TRACE
+# define DEEP_TRACE disabled_trace_
#endif
#ifndef WARN
-# define WARN
-#endif
-// ------------------------------------------------------------------------------------
-static void _cdecl _TRACE (const TCHAR* fmt, ...)
- {
- static TCHAR buffer[256];
- if (!fmt) return;
-
- va_list argptr;
- va_start (argptr, fmt);
-#if (_MSC_VER >= 1400) // VC 2005+
- _vsntprintf_s(buffer, ARRAY_ENTRIES(buffer), _TRUNCATE, fmt, argptr);
-#else
- _vsntprintf (buffer, ARRAY_ENTRIES(buffer), fmt, argptr);
+# define WARN disabled_trace_
#endif
- va_end (argptr);
- OutputDebugString(buffer);
- }
+// uncomment any of these for deeper debugging:
+//#define BEEP_DEBUG_READS
+//#define BEEP_DEBUG_WRITES
+//#define BEEP_ON_ORIENTATION_ESTIMATE
+//#define BEEP_ON_PERIODIC_STATUSREFRESH
// ------------------------------------------------------------------------------------
// wiimote
// ------------------------------------------------------------------------------------
// class statics
-HMODULE wiimote::HidDLL = NULL;
-unsigned wiimote::_TotalCreated = 0;
unsigned wiimote::_TotalConnected = 0;
-hidwrite_ptr wiimote::_HidD_SetOutputReport = NULL;
// (keep in sync with 'speaker_freq'):
const unsigned wiimote::FreqLookup [TOTAL_FREQUENCIES] =
@@ -156,23 +120,6 @@ wiimote::wiimote ()
#endif
{
_ASSERT(DataRead != INVALID_HANDLE_VALUE);
-
- // if this is the first wiimote object, detect & enable HID write support
- if(++_TotalCreated == 1)
- {
- HidDLL = LoadLibrary(_T("hid.dll"));
- _ASSERT(HidDLL);
- if(!HidDLL)
- WARN(_T("Couldn't load hid.dll - shouldn't happen!"));
- else{
- _HidD_SetOutputReport = (hidwrite_ptr)
- GetProcAddress(HidDLL, "HidD_SetOutputReport");
- if(_HidD_SetOutputReport)
- TRACE(_T("OS supports HID writes."));
- else
- TRACE(_T("OS doesn't support HID writes."));
- }
- }
// clear our public and private state data completely (including deadzones)
Clear (true);
@@ -197,7 +144,7 @@ wiimote::wiimote ()
}
// ------------------------------------------------------------------------------------
wiimote::~wiimote ()
- {
+{
Disconnect();
// events & critical sections are kept open for the lifetime of the object,
@@ -209,16 +156,8 @@ wiimote::~wiimote ()
DeleteCriticalSection(&StateLock);
// tidy up timer accuracy request
- timeEndPeriod(1);
-
- // release HID DLL (for dynamic HID write method)
- if((--_TotalCreated == 0) && HidDLL)
- {
- FreeLibrary(HidDLL);
- HidDLL = NULL;
- _HidD_SetOutputReport = NULL;
- }
- }
+ timeEndPeriod(1);
+}
// ------------------------------------------------------------------------------------
bool wiimote::Connect (unsigned wiimote_index, bool force_hidwrites)
@@ -255,7 +194,9 @@ bool wiimote::Connect (unsigned wiimote_index, bool force_hidwrites)
{
// get the buffer size for this device detail instance
DWORD req_size = 0;
- SetupDiGetDeviceInterfaceDetail(dev_info, &didata, NULL, 0, &req_size, NULL);
+ (void)SetupDiGetDeviceInterfaceDetail(dev_info, &didata, NULL, 0, &req_size, NULL);
+ if (req_size < sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA))
+ WARN(_T("couldn't get device size for %u"), index);
// (bizarre way of doing it) create a buffer large enough to hold the
// fixed-size detail struct components, and the variable string size
@@ -341,10 +282,6 @@ bool wiimote::Connect (unsigned wiimote_index, bool force_hidwrites)
// autodetect which write method the Bluetooth stack supports,
// by requesting the wiimote status report:
- if(force_hidwrites && !_HidD_SetOutputReport) {
- TRACE(_T(".. can't force HID writes (not supported)"));
- force_hidwrites = false;
- }
if(force_hidwrites)
TRACE(_T(".. (HID writes forced)"));
@@ -362,7 +299,7 @@ bool wiimote::Connect (unsigned wiimote_index, bool force_hidwrites)
}
// try HID write method (if supported)
- if(!bStatusReceived && _HidD_SetOutputReport)
+ if(!bStatusReceived)
{
bUseHIDwrite = true;
RequestStatusReport();
@@ -749,7 +686,7 @@ void wiimote::SetReportType (input_report type, bool continuous)
TYPE2NAME(IN_BUTTONS_ACCEL_EXT) :
TYPE2NAME(IN_BUTTONS_ACCEL_IR_EXT) :
TYPE2NAME(IN_BUTTONS_BALANCE_BOARD) :
- _T("(unknown??)");
+ _T("(unknown?)");
TRACE(_T("ReportType: %s (%s)"), name, (continuous? _T("continuous") :
_T("non-continuous")));
#endif
@@ -1773,50 +1710,53 @@ int wiimote::ParseReadAddress (BYTE* buff)
break;
}
- #define IF_TYPE(id) if(type == id) { \
- /* sometimes it comes in more than once */ \
- if(Internal.ExtensionType == wiimote_state::id)\
- break; \
- Internal.ExtensionType = wiimote_state::id;
+ #define IF_TYPE(id, ...) /* sometimes it comes in more than once */ \
+ if(type == id) \
+ { \
+ if(Internal.ExtensionType == wiimote_state::id) \
+ break; \
+ Internal.ExtensionType = wiimote_state::id; \
+ __VA_ARGS__; \
+ }
// MotionPlus: once it's activated & mapped to the standard ext. port
- IF_TYPE(MOTION_PLUS)
+ IF_TYPE(MOTION_PLUS,
TRACE(_T(".. Motion Plus!"));
// and start a query for the calibration data
ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16);
bMotionPlusDetected = true;
- }
- else IF_TYPE(NUNCHUK)
+ )
+ else IF_TYPE(NUNCHUK,
TRACE(_T(".. Nunchuk!"));
bMotionPlusEnabled = false;
// and start a query for the calibration data
ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16);
- }
- else IF_TYPE(CLASSIC)
+ )
+ else IF_TYPE(CLASSIC,
TRACE(_T(".. Classic Controller!"));
bMotionPlusEnabled = false;
// and start a query for the calibration data
ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16);
- }
- else IF_TYPE(GH3_GHWT_GUITAR)
+ )
+ else IF_TYPE(GH3_GHWT_GUITAR,
// sometimes it comes in more than once?
TRACE(_T(".. GH3/GHWT Guitar Controller!"));
bMotionPlusEnabled = false;
// and start a query for the calibration data
ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16);
- }
- else IF_TYPE(GHWT_DRUMS)
+ )
+ else IF_TYPE(GHWT_DRUMS,
TRACE(_T(".. GHWT Drums!"));
bMotionPlusEnabled = false;
// and start a query for the calibration data
ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16);
- }
- else IF_TYPE(BALANCE_BOARD)
+ )
+ else IF_TYPE(BALANCE_BOARD,
TRACE(_T(".. Balance Board!"));
bMotionPlusEnabled = false;
// and start a query for the calibration data
ReadAddress(REGISTER_BALANCE_CALIBRATION, 24);
- }
+ )
else if(type == PARTIALLY_INSERTED) {
// sometimes it comes in more than once?
if(Internal.ExtensionType == wiimote_state::PARTIALLY_INSERTED)
@@ -1829,13 +1769,12 @@ int wiimote::ParseReadAddress (BYTE* buff)
// status report (this usually fixes it)
Internal.bExtension = false;
RequestStatusReport();
- }
- else{
+ }
+ else
TRACE(_T("unknown extension controller found (0x%I64x)"), type);
- }
}
break;
-
+
case (REGISTER_EXTENSION_CALIBRATION & 0xffff):
case (REGISTER_BALANCE_CALIBRATION & 0xffff):
{
@@ -2040,7 +1979,7 @@ unsigned __stdcall wiimote::HIDwriteThreadfunc (void* param)
#endif
LeaveCriticalSection(&remote.HIDwriteQueueLock);
- if(!_HidD_SetOutputReport(remote.Handle, buff, REPORT_LENGTH))
+ if(!HidD_SetOutputReport(remote.Handle, buff, REPORT_LENGTH))
{
DWORD err = GetLastError();
if(err==ERROR_BUSY)
@@ -2306,7 +2245,7 @@ unsigned __stdcall wiimote::SampleStreamThreadfunc (void* param)
{
// (remember that samples are 4bit, ie. 2 per byte)
unsigned samples_left = (current_sample->length - sample_index);
- unsigned report_samples = min(samples_left, (unsigned)40);
+ unsigned report_samples = std::min(samples_left, (unsigned)40);
// round the entries up to the nearest multiple of 2
unsigned report_entries = (report_samples+1) >> 1;
@@ -2468,7 +2407,7 @@ bool wiimote::Load16bitMonoSampleWAV (const TCHAR* filepath, wiimote_sample &out
unsigned sample_freq = wf.x.nSamplesPerSec;
const unsigned epsilon = 100; // for now
- for(unsigned index=1; index<ARRAY_ENTRIES(FreqLookup); index++)
+ for(unsigned index=1; index<std::size(FreqLookup); index++)
{
if((sample_freq+epsilon) >= FreqLookup[index] &&
(sample_freq-epsilon) <= FreqLookup[index]) {
diff --git a/tracker-wii/wiiyourself/wiimote.h b/tracker-wii/wiiyourself/wiimote.h
index 3588b7c7..4b5ab42a 100644
--- a/tracker-wii/wiiyourself/wiimote.h
+++ b/tracker-wii/wiiyourself/wiimote.h
@@ -10,6 +10,7 @@
#pragma once
+#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tchar.h> // auto Unicode/Ansi support
@@ -390,11 +391,6 @@ class wiimote : public wiimote_state
EVENT DataRead; // signals overlapped read complete
bool bUseHIDwrite; // alternative write method (less efficient
// but required for some BT stacks (eg. MS')
- // HidD_SetOutputReport is only supported from XP onwards, so detect &
- // load it dynamically:
- static HMODULE HidDLL;
- static hidwrite_ptr _HidD_SetOutputReport;
-
volatile bool bStatusReceived; // for output method detection
volatile bool bConnectInProgress; // don't handle extensions until complete
volatile bool bInitInProgress; // stop regular requests until complete
@@ -405,7 +401,6 @@ volatile int MotionPlusDetectCount; // waiting for the result
volatile bool bMotionPlusEnabled;
volatile bool bMotionPlusExtension;// detected one plugged into MotionPlus
volatile bool bCalibrateAtRest; // as soon as the first sensor values // come in after a Connect() call.
- static unsigned _TotalCreated;
static unsigned _TotalConnected;
input_report ReportType; // type of data the wiimote delivers
// read buffer
diff --git a/variant/default/CMakeLists.txt b/variant/default/CMakeLists.txt
deleted file mode 100644
index a9e3c754..00000000
--- a/variant/default/CMakeLists.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-otr_module(executable EXECUTABLE BIN)
-
-set_target_properties(opentrack-executable PROPERTIES
- SUFFIX "${opentrack-binary-suffix}"
- OUTPUT_NAME "opentrack"
- PREFIX ""
-)
-
-target_link_libraries(opentrack-executable opentrack-user-interface)
diff --git a/variant/default/_variant.cmake b/variant/default/_variant.cmake
index cdf9da56..e69de29b 100644
--- a/variant/default/_variant.cmake
+++ b/variant/default/_variant.cmake
@@ -1,27 +0,0 @@
-function(otr_init_variant)
- set_property(GLOBAL PROPERTY opentrack-variant "default")
- set_property(GLOBAL PROPERTY opentrack-ident "opentrack-2.3")
-
- set(subprojects
- "tracker-*"
- "proto-*"
- "filter-*"
- "ext-*"
- "options"
- "api"
- "compat"
- "logic"
- "dinput"
- "gui"
- "main"
- "x-plane-plugin"
- "csv"
- "pose-widget"
- "spline"
- "qxt-mini"
- "macosx"
- "cv"
- "migration")
-
- set_property(GLOBAL PROPERTY opentrack-subprojects "${subprojects}")
-endfunction()
diff --git a/variant/default/main-window.cpp b/variant/default/main-window.cpp
deleted file mode 100644
index 62b969e1..00000000
--- a/variant/default/main-window.cpp
+++ /dev/null
@@ -1,929 +0,0 @@
-/* Copyright (c) 2013-2016, Stanislaw Halik <sthalik@misaki.pl>
-
- * 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 "main-window.hpp"
-#include "logic/pipeline.hpp"
-#include "options/options.hpp"
-#include "new_file_dialog.h"
-#include "migration/migration.hpp"
-#include "compat/check-visible.hpp"
-#include "compat/sleep.hpp"
-#include "compat/macros.hpp"
-#include "compat/library-path.hpp"
-#include "compat/math.hpp"
-
-#include <QMessageBox>
-#include <QDesktopServices>
-#include <QDir>
-
-extern "C" const char* const opentrack_version;
-
-#if !defined EXIT_SUCCESS
-# define EXIT_SUCCESS 0
-#endif
-
-#if !defined EXIT_FAILURE
-# define EXIT_FAILURE 1
-#endif
-
-/* FreeBSD sysexits(3)
- *
- * The input data was incorrect in some way. This
- * should only be used for user's data and not system
- * files.
- */
-
-#if !defined _WIN32 && !defined __APPLE__
-# include <unistd.h>
-void main_window::annoy_if_root()
-{
- if (geteuid() == 0)
- {
- portable::sleep(4000);
- QMessageBox::critical(this,
- tr("Running as root is bad"),
- tr("Do not run as root. Set correct device node permissions."),
- QMessageBox::Ok);
- portable::sleep(4000);
- QMessageBox::critical(this,
- tr("Running as root is bad, seriously"),
- tr("Do not run as root. I'll keep whining at every startup."),
- QMessageBox::Ok);
- portable::sleep(4000);
- QMessageBox::critical(this,
- tr("Running as root is really seriously bad"),
- tr("Do not run as root. Be annoyed, comprehensively."),
- QMessageBox::Ok);
- }
-}
-#endif
-
-main_window::main_window() :
- State(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH)
-{
- ui.setupUi(this);
-
-#if !defined _WIN32 && !defined __APPLE__
- annoy_if_root();
-#endif
-
- update_button_state(false, false);
-
- if (group::ini_directory().size() == 0)
- {
- die_on_config_not_writable();
- return;
- }
-
- if (!refresh_config_list())
- {
- exit(64);
- return;
- }
-
- connect(ui.btnEditCurves, SIGNAL(clicked()), this, SLOT(show_mapping_window()));
- connect(ui.btnShortcuts, SIGNAL(clicked()), this, SLOT(show_options_dialog()));
- connect(ui.btnShowEngineControls, SIGNAL(clicked()), this, SLOT(show_tracker_settings()));
- connect(ui.btnShowServerControls, SIGNAL(clicked()), this, SLOT(show_proto_settings()));
- connect(ui.btnShowFilterControls, SIGNAL(clicked()), this, SLOT(show_filter_settings()));
- connect(ui.btnStartTracker, SIGNAL(clicked()), this, SLOT(start_tracker_()));
- connect(ui.btnStopTracker, SIGNAL(clicked()), this, SLOT(stop_tracker_()));
- connect(ui.iconcomboProfile, &QComboBox::currentTextChanged, this, [&](const QString& x) { set_profile(x); });
-
- // fill dylib comboboxen
- {
- modules.filters().push_front(std::make_shared<dylib>("", dylib::Filter));
-
- for (std::shared_ptr<dylib>& x : modules.trackers())
- ui.iconcomboTrackerSource->addItem(x->icon, x->name);
-
- for (std::shared_ptr<dylib>& x : modules.protocols())
- ui.iconcomboProtocol->addItem(x->icon, x->name);
-
- for (std::shared_ptr<dylib>& x : modules.filters())
- ui.iconcomboFilter->addItem(x->icon, x->name);
- }
-
- // timers
- connect(&config_list_timer, &QTimer::timeout, this, [this]() { refresh_config_list(); });
- connect(&pose_update_timer, SIGNAL(timeout()), this, SLOT(show_pose()), Qt::DirectConnection);
- connect(&det_timer, SIGNAL(timeout()), this, SLOT(maybe_start_profile_from_executable()));
-
- // ctrl+q exits
- connect(&kbd_quit, SIGNAL(activated()), this, SLOT(exit()));
-
- // profile menu
- {
- profile_menu.addAction(tr("Create new empty config"), this, SLOT(make_empty_config()));
- profile_menu.addAction(tr("Create new copied config"), this, SLOT(make_copied_config()));
- profile_menu.addAction(tr("Open configuration directory"), this, SLOT(open_config_directory()));
- ui.profile_button->setMenu(&profile_menu);
- }
-
- {
- const QString cur = group::ini_filename();
- bool ok = is_config_listed(cur) ? set_profile(cur) : set_profile(OPENTRACK_DEFAULT_CONFIG);
- if (!ok)
- {
- exit(64);
- return;
- }
- }
-
- // only tie and connect main screen options after migrations are done
- // below is fine, set_profile() is called already
-
- // dylibs
- {
- connect(ui.iconcomboTrackerSource,
- &QComboBox::currentTextChanged,
- this,
- [&](const QString&) { if (pTrackerDialog) pTrackerDialog = nullptr; });
-
- connect(ui.iconcomboTrackerSource,
- &QComboBox::currentTextChanged,
- this,
- [&](const QString&) { if (pProtocolDialog) pProtocolDialog = nullptr; });
-
- connect(ui.iconcomboTrackerSource,
- &QComboBox::currentTextChanged,
- this,
- [&](const QString&) { if (pFilterDialog) pFilterDialog = nullptr; });
-
- connect(&m.tracker_dll, base_value::value_changed<QString>(), this, &main_window::save_modules, Qt::DirectConnection);
- connect(&m.protocol_dll, base_value::value_changed<QString>(), this, &main_window::save_modules, Qt::DirectConnection);
- connect(&m.filter_dll, base_value::value_changed<QString>(), this, &main_window::save_modules, Qt::DirectConnection);
-
- tie_setting(m.tracker_dll, ui.iconcomboTrackerSource);
- tie_setting(m.protocol_dll, ui.iconcomboProtocol);
- tie_setting(m.filter_dll, ui.iconcomboFilter);
- }
-
- connect(this, &main_window::start_tracker,
- this, [&]() { qDebug() << "start tracker"; start_tracker_(); },
- Qt::QueuedConnection);
-
- connect(this, &main_window::stop_tracker,
- this, [&]() { qDebug() << "stop tracker"; stop_tracker_(); },
- Qt::QueuedConnection);
-
- connect(this, &main_window::toggle_tracker,
- this, [&]() { qDebug() << "toggle tracker"; if (work) stop_tracker_(); else start_tracker_(); },
- Qt::QueuedConnection);
-
- connect(this, &main_window::restart_tracker,
- this, [&]() { qDebug() << "restart tracker"; stop_tracker_(); start_tracker_(); },
- Qt::QueuedConnection);
-
- init_tray();
- ensure_tray();
-
- register_shortcuts();
- det_timer.start(1000);
- config_list_timer.start(1000 * 5);
- kbd_quit.setEnabled(true);
-
- setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | windowFlags());
- setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
- adjustSize();
-
- if (!start_in_tray())
- {
- setVisible(true);
- show();
- }
- else
- setVisible(false);
-}
-
-void main_window::init_tray()
-{
- tray_menu.clear();
-
- QString display_name(opentrack_version);
- if (display_name.startsWith("opentrack-"))
- {
- display_name = tr("opentrack") + " " + display_name.mid(sizeof("opentrack-") - 1);
- }
- if (display_name.endsWith("-DEBUG"))
- display_name.replace(display_name.size() - int(sizeof("DEBUG")), display_name.size(), tr(" (debug)"));
-
- menu_action_header.setEnabled(false);
- menu_action_header.setText(display_name);
- menu_action_header.setIcon(QIcon(":/images/opentrack.png"));
- tray_menu.addAction(&menu_action_header);
-
- menu_action_show.setIconVisibleInMenu(true);
- menu_action_show.setText(isHidden() ? tr("Show the Octopus") : tr("Hide the Octopus"));
- menu_action_show.setIcon(QIcon(":/images/opentrack.png"));
- QObject::connect(&menu_action_show, &QAction::triggered, this, [&]() { toggle_restore_from_tray(QSystemTrayIcon::Trigger); });
- tray_menu.addAction(&menu_action_show);
-
- tray_menu.addSeparator();
-
- menu_action_tracker.setText(tr("Tracker settings"));
- menu_action_tracker.setIcon(QIcon(":/images/tools.png"));
- QObject::connect(&menu_action_tracker, &QAction::triggered, this, &main_window::show_tracker_settings);
- tray_menu.addAction(&menu_action_tracker);
-
- menu_action_filter.setText(tr("Filter settings"));
- menu_action_filter.setIcon(QIcon(":/images/filter-16.png"));
- QObject::connect(&menu_action_filter, &QAction::triggered, this, &main_window::show_filter_settings);
- tray_menu.addAction(&menu_action_filter);
-
- menu_action_proto.setText(tr("Protocol settings"));
- menu_action_proto.setIcon(QIcon(":/images/settings16.png"));
- QObject::connect(&menu_action_proto, &QAction::triggered, this, &main_window::show_proto_settings);
- tray_menu.addAction(&menu_action_proto);
-
- tray_menu.addSeparator();
-
- menu_action_mappings.setIcon(QIcon(":/images/curves.png"));
- menu_action_mappings.setText(tr("Mappings"));
- QObject::connect(&menu_action_mappings, &QAction::triggered, this, &main_window::show_mapping_window);
- tray_menu.addAction(&menu_action_mappings);
-
- menu_action_options.setIcon(QIcon(":/images/tools.png"));
- menu_action_options.setText(tr("Options"));
- QObject::connect(&menu_action_options, &QAction::triggered, this, &main_window::show_options_dialog);
- tray_menu.addAction(&menu_action_options);
-
- tray_menu.addSeparator();
-
- menu_action_exit.setText(tr("Exit"));
- QObject::connect(&menu_action_exit, &QAction::triggered, this, &main_window::exit);
- tray_menu.addAction(&menu_action_exit);
-
- connect(&s.tray_enabled,
- static_cast<void (base_value::*)(bool) const>(&base_value::valueChanged),
- this,
- &main_window::ensure_tray);
-}
-
-void main_window::register_shortcuts()
-{
- using t_key = Shortcuts::t_key;
- using t_keys = Shortcuts::t_keys;
-
- t_keys keys
- {
- t_key(s.key_start_tracking1, [&](bool) { start_tracker(); }, true),
- t_key(s.key_start_tracking2, [&](bool) { start_tracker(); }, true),
-
- t_key(s.key_stop_tracking1, [&](bool) { stop_tracker(); }, true),
- t_key(s.key_stop_tracking2, [&](bool) { stop_tracker(); }, true),
-
- t_key(s.key_toggle_tracking1, [&](bool) { toggle_tracker(); }, true),
- t_key(s.key_toggle_tracking2, [&](bool) { toggle_tracker(); }, true),
-
- t_key(s.key_restart_tracking1, [&](bool) { restart_tracker(); }, true),
- t_key(s.key_restart_tracking2, [&](bool) { restart_tracker(); }, true),
- };
-
- global_shortcuts.reload(keys);
-
- if (work)
- work->reload_shortcuts();
-}
-
-void main_window::die_on_config_not_writable()
-{
- stop_tracker_();
-
- static const QString pad(16, QChar(' '));
-
- QMessageBox::critical(this,
- tr("The Octopus is sad"),
- tr("Check permissions for your .ini directory:\n\n\"%1\"%2\n\nExiting now.").arg(group::ini_directory()).arg(pad),
- QMessageBox::Close, QMessageBox::NoButton);
-
- exit(64);
-}
-
-bool main_window::maybe_die_on_config_not_writable(const QString& current, QStringList* ini_list_)
-{
- const bool writable =
- group::with_settings_object([&](QSettings& s) {
- return s.isWritable();
- });
-
- if (writable)
- return false;
-
- const bool open = QFile(group::ini_combine(current)).open(QFile::ReadWrite);
- const QStringList ini_list = group::ini_list();
-
- if (!ini_list.contains(current) || !open)
- {
- die_on_config_not_writable();
- return true;
- }
-
- if (ini_list_ != nullptr)
- *ini_list_ = ini_list;
-
- return false;
-}
-
-bool main_window::get_new_config_name_from_dialog(QString& ret)
-{
- new_file_dialog dlg;
- dlg.exec();
- return dlg.is_ok(ret);
-}
-
-main_window::~main_window()
-{
- // stupid ps3 eye has LED issues
- if (work)
- {
- stop_tracker_();
- QEventLoop ev;
- ev.processEvents();
- portable::sleep(2000);
- }
-
- exit();
-}
-
-void main_window::set_working_directory()
-{
- QDir::setCurrent(OPENTRACK_BASE_PATH);
-}
-
-void main_window::save_modules()
-{
- m.b->save();
-}
-
-void main_window::make_empty_config()
-{
- QString name;
- if (get_new_config_name_from_dialog(name))
- {
- QFile(group::ini_combine(name)).open(QFile::ReadWrite);
-
- if (!refresh_config_list())
- return;
-
- if (is_config_listed(name))
- {
- QSignalBlocker q(ui.iconcomboProfile);
-
- if (!set_profile(name))
- return;
- mark_config_as_not_needing_migration();
- }
- }
-}
-
-void main_window::make_copied_config()
-{
- const QString cur = group::ini_pathname();
- QString name;
- if (cur != "" && get_new_config_name_from_dialog(name))
- {
- const QString new_name = group::ini_combine(name);
- (void) QFile::remove(new_name);
- QFile::copy(cur, new_name);
-
- if (!refresh_config_list())
- return;
-
- if (is_config_listed(name))
- {
- QSignalBlocker q(ui.iconcomboProfile);
-
- if (!set_profile(name))
- return;
- mark_config_as_not_needing_migration();
- }
- }
-
-}
-
-void main_window::open_config_directory()
-{
- QDesktopServices::openUrl("file:///" + QDir::toNativeSeparators(group::ini_directory()));
-}
-
-bool main_window::refresh_config_list()
-{
- if (work)
- return true;
-
- QStringList ini_list = group::ini_list();
-
- // check for sameness
- const bool exact_same = ini_list.size() > 0 && progn(
- if (ini_list.size() == ui.iconcomboProfile->count())
- {
- const int sz = ini_list.size();
- for (int i = 0; i < sz; i++)
- {
- if (ini_list[i] != ui.iconcomboProfile->itemText(i))
- return false;
- }
- return true;
- }
-
- return false;
- );
-
- QString current = group::ini_filename();
-
- if (!ini_list.contains(current))
- current = OPENTRACK_DEFAULT_CONFIG_Q;
-
- if (maybe_die_on_config_not_writable(current, &ini_list))
- return false;
-
- if (exact_same)
- return true;
-
- const QIcon icon(":/images/settings16.png");
-
- QSignalBlocker l(ui.iconcomboProfile);
-
- ui.iconcomboProfile->clear();
- ui.iconcomboProfile->addItems(ini_list);
-
- for (int i = 0; i < ini_list.size(); i++)
- ui.iconcomboProfile->setItemIcon(i, icon);
-
- ui.iconcomboProfile->setCurrentText(current);
-
- return true;
-}
-
-void main_window::update_button_state(bool running, bool inertialp)
-{
- bool not_running = !running;
- ui.iconcomboProfile->setEnabled(not_running);
- ui.btnStartTracker->setEnabled(not_running);
- ui.btnStopTracker->setEnabled(running);
- ui.iconcomboProtocol->setEnabled(not_running);
- ui.iconcomboFilter->setEnabled(not_running);
- ui.iconcomboTrackerSource->setEnabled(not_running);
- ui.profile_button->setEnabled(not_running);
- ui.video_frame_label->setVisible(not_running || inertialp);
- if(not_running)
- {
- ui.video_frame_label->setPixmap(QPixmap(":/images/tracking-not-started.png"));
- }
- else {
- ui.video_frame_label->setPixmap(QPixmap(":/images/no-feed.png"));
- }
-}
-
-void main_window::start_tracker_()
-{
- if (work)
- return;
-
- {
- double p[6] = {0,0,0, 0,0,0};
- display_pose(p, p);
- }
-
- work = std::make_shared<Work>(pose, ev, ui.video_frame, current_tracker(), current_protocol(), current_filter());
-
- if (!work->is_ok())
- {
- work = nullptr;
- return;
- }
-
- if (pTrackerDialog)
- pTrackerDialog->register_tracker(work->libs.pTracker.get());
-
- if (pFilterDialog)
- pFilterDialog->register_filter(work->libs.pFilter.get());
-
- if (pProtocolDialog)
- pProtocolDialog->register_protocol(work->libs.pProtocol.get());
-
- pose_update_timer.start(50);
-
- // NB check valid since SelectedLibraries ctor called
- // trackers take care of layout state updates
- const bool is_inertial = ui.video_frame->layout() == nullptr;
- update_button_state(true, is_inertial);
-
- ui.btnStopTracker->setFocus();
-}
-
-void main_window::stop_tracker_()
-{
- if (!work)
- return;
-
- with_tracker_teardown sentinel;
-
- pose_update_timer.stop();
- ui.pose_display->rotate_sync(0,0,0, 0,0,0);
-
- if (pTrackerDialog)
- pTrackerDialog->unregister_tracker();
-
- if (pProtocolDialog)
- pProtocolDialog->unregister_protocol();
-
- if (pFilterDialog)
- pFilterDialog->unregister_filter();
-
- work = nullptr;
-
- {
- double p[6] = {0,0,0, 0,0,0};
- display_pose(p, p);
- }
-
- update_button_state(false, false);
- set_title();
- ui.btnStartTracker->setFocus();
-}
-
-void main_window::display_pose(const double *mapped, const double *raw)
-{
- ui.pose_display->rotate_async(mapped[Yaw], mapped[Pitch], -mapped[Roll],
- mapped[TX], mapped[TY], mapped[TZ]);
-
- QLCDNumber* raw_[] = {
- ui.raw_x, ui.raw_y, ui.raw_z,
- ui.raw_yaw, ui.raw_pitch, ui.raw_roll,
- };
-
- QLCDNumber* mapped_[] = {
- ui.pose_x, ui.pose_y, ui.pose_z,
- ui.pose_yaw, ui.pose_pitch, ui.pose_roll,
- };
-
- for (int k = 0; k < 6; k++)
- {
- raw_[k]->display(iround(raw[k]));
- mapped_[k]->display(iround(mapped[k]));
- }
-
- QString game_title;
- if (work && work->libs.pProtocol)
- game_title = work->libs.pProtocol->game_name();
- set_title(game_title);
-}
-
-void main_window::set_title(const QString& game_title_)
-{
- QString game_title;
- if (game_title_ != "")
- game_title = tr(" :: ") + game_title_;
- QString current = group::ini_filename();
- QString version(opentrack_version);
- version = tr("opentrack") + " " + version.mid(sizeof("opentrack-") - 1);
- setWindowTitle(version + tr(" :: ") + current + game_title);
-}
-
-void main_window::show_pose()
-{
- set_is_visible(*this);
-
- if (mapping_widget)
- mapping_widget->refresh_tab();
-
- if (!check_is_visible())
- return;
-
- double mapped[6], raw[6];
-
- work->tracker->raw_and_mapped_pose(mapped, raw);
-
- display_pose(mapped, raw);
-}
-
-template<typename t, typename F>
-bool main_window::mk_window_common(std::unique_ptr<t>& d, F&& ctor)
-{
- if (d)
- {
- d->show();
- d->raise();
-
- return false;
- }
- else if ((d = std::unique_ptr<t>(ctor())))
- {
- QWidget& w = *d;
-
- w.setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | w.windowFlags());
- w.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
-
- w.show();
- w.adjustSize();
-
- return true;
- }
-
- return false;
-}
-
-template<typename t, typename... Args>
-inline bool main_window::mk_window(std::unique_ptr<t>& place, Args&&... params)
-{
- return mk_window_common(place, [&]() { return new t(params...); });
-}
-
-template<typename t>
-bool main_window::mk_dialog(std::shared_ptr<dylib> lib, std::unique_ptr<t>& d)
-{
- const bool just_created = mk_window_common(d, [&]() -> t* {
- if (lib && lib->Dialog)
- return (t*) lib->Dialog();
- return nullptr;
- });
-
- return just_created;
-}
-
-void main_window::show_tracker_settings()
-{
- if (mk_dialog(current_tracker(), pTrackerDialog) && work && work->libs.pTracker)
- pTrackerDialog->register_tracker(work->libs.pTracker.get());
- if (pTrackerDialog)
- QObject::connect(pTrackerDialog.get(), &ITrackerDialog::closing,
- this, [this]() { pTrackerDialog = nullptr; });
-}
-
-void main_window::show_proto_settings()
-{
- if (mk_dialog(current_protocol(), pProtocolDialog) && work && work->libs.pProtocol)
- pProtocolDialog->register_protocol(work->libs.pProtocol.get());
- if (pProtocolDialog)
- QObject::connect(pProtocolDialog.get(), &IProtocolDialog::closing,
- this, [this]() { pProtocolDialog = nullptr; });
-}
-
-void main_window::show_filter_settings()
-{
- if (mk_dialog(current_filter(), pFilterDialog) && work && work->libs.pFilter)
- pFilterDialog->register_filter(work->libs.pFilter.get());
- if (pFilterDialog)
- QObject::connect(pFilterDialog.get(), &IFilterDialog::closing,
- this, [this]() { pFilterDialog = nullptr; });
-}
-
-void main_window::show_options_dialog()
-{
- if (mk_window(options_widget, [&](bool flag) { set_keys_enabled(!flag); }))
- {
- // XXX this should logically connect to a bundle
- // also doesn't work when switching profiles with options dialog open
- // move shortcuts to a separate bundle and add a migration -sh 20180218
- connect(options_widget.get(), &options_dialog::closing,
- this, &main_window::register_shortcuts);
- }
-}
-
-void main_window::show_mapping_window()
-{
- mk_window(mapping_widget, pose);
-}
-
-void main_window::exit(int status)
-{
- QApplication::setQuitOnLastWindowClosed(true);
- if (tray)
- tray->hide();
- tray = nullptr;
- close();
- QApplication::exit(status);
-}
-
-bool main_window::set_profile(const QString& new_name_)
-{
- if (!refresh_config_list())
- return false;
-
- QString new_name = new_name_;
-
- if (!is_config_listed(new_name))
- new_name = OPENTRACK_DEFAULT_CONFIG_Q;
-
- if (maybe_die_on_config_not_writable(new_name, nullptr))
- return false;
-
- ui.iconcomboProfile->setCurrentText(new_name);
- set_profile_in_registry(new_name);
-
- set_title();
- options::detail::bundler::refresh_all_bundles();
-
- // migrations are for config layout changes and other user-visible
- // incompatibilities in future versions
- run_migrations();
-
- return true;
-}
-
-void main_window::ensure_tray()
-{
- if (!QSystemTrayIcon::isSystemTrayAvailable())
- {
- QApplication::setQuitOnLastWindowClosed(true);
- return;
- }
-
- if (s.tray_enabled)
- {
- if (!tray)
- {
- tray = std::make_unique<QSystemTrayIcon>(this);
- tray->setIcon(QIcon(":/images/opentrack.png"));
- tray->setContextMenu(&tray_menu);
- tray->show();
-
- connect(tray.get(),
- &QSystemTrayIcon::activated,
- this,
- &main_window::toggle_restore_from_tray);
- }
-
- QApplication::setQuitOnLastWindowClosed(false);
- }
- else
- {
- const bool is_hidden = isHidden() || !isVisible();
-
- if (is_hidden)
- {
- show();
- setVisible(true);
-
- raise(); // for OSX
- activateWindow(); // for Windows
- }
-
- if (tray)
- tray->hide();
- tray = nullptr;
-
- QApplication::setQuitOnLastWindowClosed(true);
- }
-}
-
-void main_window::toggle_restore_from_tray(QSystemTrayIcon::ActivationReason e)
-{
- if (progn(
- switch (e)
- {
- // if we enable double click also then it causes
- // toggle back to the original state
- //case QSystemTrayIcon::DoubleClick:
- case QSystemTrayIcon::Trigger: // single click
- return false;
- default:
- return true;
- }
- ))
- {
- return;
- }
-
- ensure_tray();
-
- const bool is_minimized = isHidden() || !is_tray_enabled();
-
- menu_action_show.setText(!isHidden() ? tr("Show the Octopus") : tr("Hide the Octopus"));
-
- setVisible(is_minimized);
- setHidden(!is_minimized);
-
- setWindowState(progn(
- using ws = Qt::WindowStates;
- if (is_minimized)
- return ws(windowState() & (~Qt::WindowMinimized));
- else
- return ws(Qt::WindowNoState);
- ));
-
- if (is_minimized)
- {
- raise(); // for OSX
- activateWindow(); // for Windows
- }
- else
- {
- lower();
- clearFocus();
- }
-}
-
-bool main_window::maybe_hide_to_tray(QEvent* e)
-{
- if (e->type() == QEvent::WindowStateChange &&
- (windowState() & Qt::WindowMinimized) &&
- is_tray_enabled())
- {
- e->accept();
- ensure_tray();
- hide();
-
- return true;
- }
-
- return false;
-}
-
-void main_window::closeEvent(QCloseEvent*)
-{
- exit();
-}
-
-void main_window::maybe_start_profile_from_executable()
-{
- if (!work)
- {
- QString prof;
- if (det.config_to_start(prof))
- {
- set_profile(prof);
- start_tracker_();
- }
- }
- else
- {
- if (det.should_stop())
- stop_tracker_();
- }
-}
-
-void main_window::set_keys_enabled(bool flag)
-{
- if (!flag)
- {
- if (work)
- work->sc->reload({});
- global_shortcuts.reload({});
- }
- else
- {
- register_shortcuts();
- }
-}
-
-bool main_window::is_config_listed(const QString& name)
-{
- const int sz = ui.iconcomboProfile->count();
- for (int i = 0; i < sz; i++)
- if (ui.iconcomboProfile->itemText(i) == name)
- return true;
- return false;
-}
-
-void main_window::changeEvent(QEvent* e)
-{
- if (!maybe_hide_to_tray(e))
- e->ignore();
-}
-
-bool main_window::event(QEvent* event)
-{
- using t = QEvent::Type;
-
- if (work)
- {
- switch (event->type())
- {
- case t::Hide:
- case t::WindowActivate:
- case t::WindowDeactivate:
- case t::WindowStateChange:
- case t::FocusIn:
- set_is_visible(*this, true);
- /*FALLTHROUGH*/
- default:
- break;
- }
- }
- return QMainWindow::event(event);
-}
-
-bool main_window::is_tray_enabled()
-{
- return s.tray_enabled && QSystemTrayIcon::isSystemTrayAvailable();
-}
-
-bool main_window::start_in_tray()
-{
- return s.tray_enabled && s.tray_start && QSystemTrayIcon::isSystemTrayAvailable();
-}
-
-void main_window::set_profile_in_registry(const QString &profile)
-{
- group::with_global_settings_object([&](QSettings& s) {
- s.setValue(OPENTRACK_CONFIG_FILENAME_KEY, profile);
- });
-}
diff --git a/variant/default/main-window.ui b/variant/default/main-window.ui
deleted file mode 100644
index 7aab90e8..00000000
--- a/variant/default/main-window.ui
+++ /dev/null
@@ -1,1459 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <author>Lovecraftian Octopus</author>
- <class>main_window</class>
- <widget class="QMainWindow" name="main_window">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>646</width>
- <height>517</height>
- </rect>
- </property>
- <property name="windowIcon">
- <iconset resource="../../gui/opentrack-res.qrc">
- <normaloff>:/images/opentrack.png</normaloff>:/images/opentrack.png</iconset>
- </property>
- <property name="styleSheet">
- <string notr="true">#video_feed { border: 0; }
-</string>
- </property>
- <widget class="QWidget" name="content">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_5">
- <property name="spacing">
- <number>0</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>6</number>
- </property>
- <property name="bottomMargin">
- <number>6</number>
- </property>
- <item>
- <widget class="QFrame" name="frame">
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="lineWidth">
- <number>0</number>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <property name="spacing">
- <number>0</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item alignment="Qt::AlignLeft|Qt::AlignTop">
- <widget class="QWidget" name="top" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <property name="spacing">
- <number>0</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>4</number>
- </property>
- <item alignment="Qt::AlignLeft|Qt::AlignTop">
- <widget class="QFrame" name="video_feed">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <property name="lineWidth">
- <number>0</number>
- </property>
- <widget class="QFrame" name="video_frame">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>320</width>
- <height>240</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <widget class="QLabel" name="video_frame_label">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>320</width>
- <height>240</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <property name="font">
- <font>
- <family>Candara</family>
- <pointsize>37</pointsize>
- <weight>50</weight>
- <bold>false</bold>
- <kerning>true</kerning>
- </font>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="../../gui/opentrack-res.qrc">:/images/tracking-not-started.png</pixmap>
- </property>
- <property name="scaledContents">
- <bool>false</bool>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </widget>
- </widget>
- </item>
- <item>
- <widget class="pose_widget" name="pose_display" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QFrame" name="top_display">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="lineWidth">
- <number>0</number>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <property name="spacing">
- <number>6</number>
- </property>
- <property name="leftMargin">
- <number>5</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>6</number>
- </property>
- <item>
- <widget class="QGroupBox" name="box_raw_headpose">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Raw tracker data</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_12">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <property name="spacing">
- <number>0</number>
- </property>
- <item row="0" column="3">
- <widget class="QLCDNumber" name="raw_yaw">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <stylestrategy>NoAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Plain</enum>
- </property>
- <property name="lineWidth">
- <number>1</number>
- </property>
- <property name="smallDecimalPoint">
- <bool>true</bool>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Outline</enum>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="lblZ_4">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="autoFillBackground">
- <bool>false</bool>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="text">
- <string>Z</string>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QLabel" name="lblRotY_4">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="text">
- <string>Pitch</string>
- </property>
- </widget>
- </item>
- <item row="1" column="3">
- <widget class="QLCDNumber" name="raw_pitch">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <stylestrategy>NoAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Plain</enum>
- </property>
- <property name="lineWidth">
- <number>1</number>
- </property>
- <property name="smallDecimalPoint">
- <bool>true</bool>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Outline</enum>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="lblY_4">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="autoFillBackground">
- <bool>false</bool>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="text">
- <string>Y</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="lblX_4">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="text">
- <string>X</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLCDNumber" name="raw_x">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <stylestrategy>NoAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Plain</enum>
- </property>
- <property name="lineWidth">
- <number>1</number>
- </property>
- <property name="smallDecimalPoint">
- <bool>true</bool>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Outline</enum>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QLabel" name="lblRotZ_4">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="text">
- <string>Roll</string>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QLabel" name="lblRotX_4">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="text">
- <string>Yaw</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLCDNumber" name="raw_y">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <stylestrategy>NoAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Plain</enum>
- </property>
- <property name="lineWidth">
- <number>1</number>
- </property>
- <property name="smallDecimalPoint">
- <bool>true</bool>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Outline</enum>
- </property>
- </widget>
- </item>
- <item row="2" column="3">
- <widget class="QLCDNumber" name="raw_roll">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <stylestrategy>NoAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Plain</enum>
- </property>
- <property name="lineWidth">
- <number>1</number>
- </property>
- <property name="smallDecimalPoint">
- <bool>true</bool>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Outline</enum>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLCDNumber" name="raw_z">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <stylestrategy>NoAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Plain</enum>
- </property>
- <property name="lineWidth">
- <number>1</number>
- </property>
- <property name="smallDecimalPoint">
- <bool>true</bool>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Outline</enum>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="box_mapped_headpose">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Game data</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_14">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <property name="spacing">
- <number>0</number>
- </property>
- <item row="1" column="3">
- <widget class="QLCDNumber" name="pose_pitch">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <stylestrategy>NoAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="smallDecimalPoint">
- <bool>true</bool>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Flat</enum>
- </property>
- </widget>
- </item>
- <item row="0" column="3">
- <widget class="QLCDNumber" name="pose_yaw">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <stylestrategy>NoAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="smallDecimalPoint">
- <bool>true</bool>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Flat</enum>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLCDNumber" name="pose_z">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <stylestrategy>NoAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="smallDecimalPoint">
- <bool>true</bool>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Flat</enum>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="lblx">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="text">
- <string>X</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="lblY_2">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="autoFillBackground">
- <bool>false</bool>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="text">
- <string>Y</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLCDNumber" name="pose_x">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <stylestrategy>NoAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="smallDecimalPoint">
- <bool>true</bool>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Flat</enum>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QLabel" name="lblRotY_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="text">
- <string>Pitch</string>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QLabel" name="lblRotZ_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="text">
- <string>Roll</string>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QLabel" name="lblRotX_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="text">
- <string>Yaw</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="lblZ_2">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="autoFillBackground">
- <bool>false</bool>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <property name="text">
- <string>Z</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLCDNumber" name="pose_y">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <stylestrategy>NoAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="smallDecimalPoint">
- <bool>true</bool>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Flat</enum>
- </property>
- </widget>
- </item>
- <item row="2" column="3">
- <widget class="QLCDNumber" name="pose_roll">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <stylestrategy>NoAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="smallDecimalPoint">
- <bool>true</bool>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Flat</enum>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QFrame" name="bottom_controls">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <property name="leftMargin">
- <number>6</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <property name="spacing">
- <number>6</number>
- </property>
- <item row="0" column="1">
- <widget class="QFrame" name="groupWindows">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>4</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="lineWidth">
- <number>0</number>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_3">
- <property name="spacing">
- <number>3</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QFrame" name="groupProfile">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <property name="spacing">
- <number>0</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QToolButton" name="profile_button">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::StrongFocus</enum>
- </property>
- <property name="text">
- <string>Profile</string>
- </property>
- <property name="popupMode">
- <enum>QToolButton::InstantPopup</enum>
- </property>
- <property name="toolButtonStyle">
- <enum>Qt::ToolButtonTextBesideIcon</enum>
- </property>
- <property name="autoRaise">
- <bool>true</bool>
- </property>
- <property name="arrowType">
- <enum>Qt::DownArrow</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="iconcomboProfile">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::StrongFocus</enum>
- </property>
- <property name="maxVisibleItems">
- <number>20</number>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="btnShortcuts">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Options</string>
- </property>
- <property name="icon">
- <iconset resource="../../gui/opentrack-res.qrc">
- <normaloff>:/images/tools.png</normaloff>:/images/tools.png</iconset>
- </property>
- <property name="iconSize">
- <size>
- <width>80</width>
- <height>24</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="btnEditCurves">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Mapping</string>
- </property>
- <property name="icon">
- <iconset resource="../../gui/opentrack-res.qrc">
- <normaloff>:/images/curves.png</normaloff>:/images/curves.png</iconset>
- </property>
- <property name="iconSize">
- <size>
- <width>80</width>
- <height>24</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupStartStop">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>3</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Tracking</string>
- </property>
- <property name="flat">
- <bool>true</bool>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_4">
- <property name="spacing">
- <number>8</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>6</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>6</number>
- </property>
- <item>
- <widget class="QToolButton" name="btnStartTracker">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <weight>75</weight>
- <bold>true</bold>
- </font>
- </property>
- <property name="text">
- <string>Start</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QToolButton" name="btnStopTracker">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <weight>75</weight>
- <bold>true</bold>
- </font>
- </property>
- <property name="text">
- <string>Stop</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QFrame" name="frame_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>4</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="spacing">
- <number>2</number>
- </property>
- <property name="leftMargin">
- <number>3</number>
- </property>
- <property name="topMargin">
- <number>2</number>
- </property>
- <property name="rightMargin">
- <number>3</number>
- </property>
- <property name="bottomMargin">
- <number>8</number>
- </property>
- <item>
- <widget class="QGroupBox" name="groupTrackerSource">
- <property name="title">
- <string>Input</string>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <property name="leftMargin">
- <number>4</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <property name="horizontalSpacing">
- <number>3</number>
- </property>
- <property name="verticalSpacing">
- <number>0</number>
- </property>
- <item row="0" column="0">
- <widget class="QComboBox" name="iconcomboTrackerSource">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::TabFocus</enum>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QToolButton" name="btnShowEngineControls">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <family>DejaVu Sans</family>
- <stylestrategy>PreferAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="focusPolicy">
- <enum>Qt::ClickFocus</enum>
- </property>
- <property name="text">
- <string>🔨</string>
- </property>
- <property name="flat" stdset="0">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupGameProtocol">
- <property name="title">
- <string>Output</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_4">
- <property name="leftMargin">
- <number>4</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <property name="horizontalSpacing">
- <number>3</number>
- </property>
- <property name="verticalSpacing">
- <number>0</number>
- </property>
- <item row="0" column="0">
- <widget class="QComboBox" name="iconcomboProtocol">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::TabFocus</enum>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QToolButton" name="btnShowServerControls">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <family>DejaVu Sans</family>
- <stylestrategy>PreferAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="focusPolicy">
- <enum>Qt::ClickFocus</enum>
- </property>
- <property name="text">
- <string>🔨</string>
- </property>
- <property name="flat" stdset="0">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupFilter">
- <property name="title">
- <string>Filter</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_3">
- <property name="leftMargin">
- <number>4</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <property name="horizontalSpacing">
- <number>3</number>
- </property>
- <property name="verticalSpacing">
- <number>0</number>
- </property>
- <item row="0" column="0">
- <widget class="QComboBox" name="iconcomboFilter">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::TabFocus</enum>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QToolButton" name="btnShowFilterControls">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <family>DejaVu Sans</family>
- <stylestrategy>PreferAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="focusPolicy">
- <enum>Qt::ClickFocus</enum>
- </property>
- <property name="text">
- <string>🔨</string>
- </property>
- <property name="flat" stdset="0">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </widget>
- <customwidgets>
- <customwidget>
- <class>pose_widget</class>
- <extends>QWidget</extends>
- <header>pose-widget/pose-widget.hpp</header>
- </customwidget>
- </customwidgets>
- <tabstops>
- <tabstop>btnStartTracker</tabstop>
- <tabstop>btnStopTracker</tabstop>
- <tabstop>profile_button</tabstop>
- <tabstop>iconcomboProfile</tabstop>
- <tabstop>btnShortcuts</tabstop>
- <tabstop>btnEditCurves</tabstop>
- </tabstops>
- <resources>
- <include location="../../gui/opentrack-res.qrc"/>
- </resources>
- <connections/>
-</ui>
diff --git a/variant/trackmouse/CMakeLists.txt b/variant/trackmouse/CMakeLists.txt
deleted file mode 100644
index c57479d4..00000000
--- a/variant/trackmouse/CMakeLists.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-otr_module(executable EXECUTABLE BIN)
-
-set_target_properties(opentrack-executable PROPERTIES
- SUFFIX "${opentrack-binary-suffix}"
- OUTPUT_NAME "trackmouse"
- PREFIX ""
-)
-
-target_link_libraries(opentrack-executable opentrack-user-interface)
diff --git a/variant/trackmouse/_variant.cmake b/variant/trackmouse/_variant.cmake
deleted file mode 100644
index b54ecf52..00000000
--- a/variant/trackmouse/_variant.cmake
+++ /dev/null
@@ -1,19 +0,0 @@
-function(otr_init_variant)
- set_property(GLOBAL PROPERTY opentrack-variant "trackmouse")
- set_property(GLOBAL PROPERTY opentrack-ident "trackmouse-prototype")
- set(subprojects
- "tracker-pt"
- "proto-mouse"
- "filter-accela"
- "options"
- "api"
- "compat"
- "logic"
- "dinput"
- "gui"
- "pose-widget"
- "spline"
- "cv"
- "migration")
- set_property(GLOBAL PROPERTY opentrack-subprojects "${subprojects}")
-endfunction()
diff --git a/variant/trackmouse/images/start.png b/variant/trackmouse/images/start.png
deleted file mode 100644
index b8e6f271..00000000
--- a/variant/trackmouse/images/start.png
+++ /dev/null
Binary files differ
diff --git a/variant/trackmouse/images/stop.png b/variant/trackmouse/images/stop.png
deleted file mode 100644
index 0ff13bd5..00000000
--- a/variant/trackmouse/images/stop.png
+++ /dev/null
Binary files differ
diff --git a/variant/trackmouse/main.cpp b/variant/trackmouse/main.cpp
deleted file mode 100644
index 0128683e..00000000
--- a/variant/trackmouse/main.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include "gui/init.hpp"
-#include "window.hpp"
-
-#if defined _WIN32
-# include <windows.h>
-#endif
-
-int main(int argc, char** argv)
-{
- return run_application(argc, argv, []() { return new window; });
-}
-
-#if defined _MSC_VER
-int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int /* nCmdShow */)
-{
- return main(__argc, __argv);
-}
-#endif
diff --git a/variant/trackmouse/trackmouse-res.qrc b/variant/trackmouse/trackmouse-res.qrc
deleted file mode 100644
index f351b3f2..00000000
--- a/variant/trackmouse/trackmouse-res.qrc
+++ /dev/null
@@ -1,6 +0,0 @@
-<RCC>
- <qresource prefix="/images">
- <file>images/start.png</file>
- <file>images/stop.png</file>
- </qresource>
-</RCC>
diff --git a/variant/trackmouse/trackmouse-settings.cpp b/variant/trackmouse/trackmouse-settings.cpp
deleted file mode 100644
index 1df88a2d..00000000
--- a/variant/trackmouse/trackmouse-settings.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-#include "logic/main-settings.hpp"
-#include "logic/mappings.hpp"
-
-#include "tracker-pt/pt-settings.hpp"
-#include "filter-accela/accela-settings.hpp"
-#include "proto-mouse/mouse-settings.hpp"
-
-#include "options/options.hpp"
-
-#include <QSettings>
-
-using namespace options;
-
-static void force_spline_settings()
-{
- main_settings main;
-
- axis_opts** all_axis_opts = main.all_axis_opts;
- Mappings mappings { all_axis_opts };
-
- for (unsigned k = 0; k < 6; k++)
- {
- Map& map = mappings(k);
- const QString& prefix = all_axis_opts[k]->prefix();
-
- const QString& name1 = map.name;
- const QString& name2 = map.alt_name;
-
- bundle b = make_bundle(prefix);
-
- spline_detail::settings s1(b, name1, Axis(k));
- spline_detail::settings s2(b, name2, Axis(k));
-
- s1.points = QList<QPointF> { { 180, 180 } };
- s2.points = QList<QPointF> { { 180, 180 } };
-
- b->save();
- }
-}
-
-static void force_main_settings()
-{
- main_settings s;
- s.center_at_startup = true;
- s.reltrans_mode = reltrans_disabled;
- s.neck_enable = false;
-
- module_settings m;
-
- m.tracker_dll = "PointTracker 1.1";
- m.protocol_dll = "Mouse";
- m.filter_dll = "Accela";
-
- s.b->save();
- s.b_map->save();
-}
-
-static void force_pt_settings()
-{
- pt_settings s("tracker-pt");
-
- enum { Clip = 0 };
-
- s.active_model_panel = Clip;
- // XXX TODO trackmouse clip sizes
-
- s.cam_fps = 60;
- s.cam_res_x = 640;
- s.cam_res_y = 480;
- s.camera_name = "PS3Eye Camera";
-
- s.min_point_size = 6;
- s.max_point_size = 15;
-
- // XXX TODO auto threshold slider position
- s.auto_threshold = true;
-
- s.t_MH_x = 0, s.t_MH_y = 0, s.t_MH_z = 0;
- s.blob_color = pt_color_natural;
- s.fov = 56;
- s.dynamic_pose = false;
-
- s.b->save();
-}
-
-static void force_mouse_settings()
-{
- mouse_settings s;
-
- s.Mouse_X = Yaw + 1;
- s.Mouse_Y = Pitch + 1;
-
- s.b->save();
-}
-
-static void force_accela_settings()
-{
- // TODO
-}
-
-void force_trackmouse_settings()
-{
- group::with_settings_object([](QSettings&) { // batch config save
- force_main_settings();
- force_spline_settings();
- force_pt_settings();
- force_mouse_settings();
- force_accela_settings();
- });
-}
diff --git a/variant/trackmouse/trackmouse.ico b/variant/trackmouse/trackmouse.ico
deleted file mode 100644
index 5cac8da1..00000000
--- a/variant/trackmouse/trackmouse.ico
+++ /dev/null
Binary files differ
diff --git a/variant/trackmouse/trackmouse.rc b/variant/trackmouse/trackmouse.rc
deleted file mode 100644
index 8df1e9b1..00000000
--- a/variant/trackmouse/trackmouse.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-#include <windows.h>
-IDI_ICON1 ICON "trackmouse.ico"
diff --git a/variant/trackmouse/window.cpp b/variant/trackmouse/window.cpp
deleted file mode 100644
index 73f91c74..00000000
--- a/variant/trackmouse/window.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "window.hpp"
-
-#include <QApplication>
-
-void force_trackmouse_settings();
-
-void window::closeEvent(QCloseEvent* e)
-{
- e->accept();
- QApplication::exit(0);
-}
-
-window::window() : QMainWindow()
-{
- ui.setupUi(this);
- setAttribute(Qt::WA_QuitOnClose);
-
- force_trackmouse_settings();
-
- show();
-}
-
diff --git a/variant/trackmouse/window.hpp b/variant/trackmouse/window.hpp
deleted file mode 100644
index e5c34410..00000000
--- a/variant/trackmouse/window.hpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include "ui_window.h"
-#include <QMainWindow>
-#include <QCloseEvent>
-
-class window : public QMainWindow
-{
- Q_OBJECT
-
- Ui::window ui;
-
- void closeEvent(QCloseEvent* e) override;
-public:
- window();
-
-};
diff --git a/variant/trackmouse/window.ui b/variant/trackmouse/window.ui
deleted file mode 100644
index 9501c5c2..00000000
--- a/variant/trackmouse/window.ui
+++ /dev/null
@@ -1,420 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>window</class>
- <widget class="QMainWindow" name="window">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>755</width>
- <height>240</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>240</height>
- </size>
- </property>
- <property name="windowTitle">
- <string>trackmouse prototype</string>
- </property>
- <widget class="QWidget" name="frame">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>240</height>
- </size>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <property name="spacing">
- <number>6</number>
- </property>
- <property name="leftMargin">
- <number>6</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>12</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QFrame" name="video_feed">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <widget class="QFrame" name="video_frame">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>320</width>
- <height>240</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <widget class="QLabel" name="video_frame_label">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>320</width>
- <height>240</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <property name="font">
- <font>
- <family>Candara</family>
- <pointsize>37</pointsize>
- <weight>50</weight>
- <bold>false</bold>
- <kerning>true</kerning>
- </font>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="../../gui/opentrack-res.qrc">:/images/tracking-not-started.png</pixmap>
- </property>
- <property name="scaledContents">
- <bool>false</bool>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </widget>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="widget_3" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <property name="spacing">
- <number>12</number>
- </property>
- <property name="leftMargin">
- <number>8</number>
- </property>
- <property name="topMargin">
- <number>12</number>
- </property>
- <property name="rightMargin">
- <number>12</number>
- </property>
- <property name="bottomMargin">
- <number>12</number>
- </property>
- <item>
- <widget class="QGroupBox" name="groupBox_3">
- <property name="title">
- <string>Keyboard shortcuts</string>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <property name="leftMargin">
- <number>12</number>
- </property>
- <property name="topMargin">
- <number>6</number>
- </property>
- <property name="rightMargin">
- <number>40</number>
- </property>
- <property name="bottomMargin">
- <number>6</number>
- </property>
- <property name="horizontalSpacing">
- <number>30</number>
- </property>
- <property name="verticalSpacing">
- <number>6</number>
- </property>
- <item row="0" column="0">
- <widget class="QLabel" name="label_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <pointsize>12</pointsize>
- <weight>75</weight>
- <bold>true</bold>
- <stylestrategy>PreferAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="text">
- <string>start/stop tracking</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1" alignment="Qt::AlignRight">
- <widget class="QLabel" name="label_3">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <pointsize>12</pointsize>
- <weight>75</weight>
- <bold>true</bold>
- <stylestrategy>PreferAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="text">
- <string>Alt+F10</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_4">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <pointsize>12</pointsize>
- <weight>75</weight>
- <bold>true</bold>
- <stylestrategy>PreferAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="text">
- <string>center</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1" alignment="Qt::AlignRight">
- <widget class="QLabel" name="label_5">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <pointsize>12</pointsize>
- <weight>75</weight>
- <bold>true</bold>
- <stylestrategy>PreferAntialias</stylestrategy>
- <kerning>false</kerning>
- </font>
- </property>
- <property name="text">
- <string>Alt+F12</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Sensitivity</string>
- </property>
- <layout class="QHBoxLayout">
- <item>
- <widget class="QSlider" name="sensitivity_slider">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>10</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="pageStep">
- <number>1</number>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="tickPosition">
- <enum>QSlider::TicksAbove</enum>
- </property>
- <property name="tickInterval">
- <number>10</number>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="sensitivity_label">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>2</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>100%</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="widget" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <property name="topMargin">
- <number>3</number>
- </property>
- <property name="bottomMargin">
- <number>3</number>
- </property>
- <item>
- <widget class="QCommandLinkButton" name="start_button">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Start</string>
- </property>
- <property name="icon">
- <iconset resource="trackmouse-res.qrc">
- <normaloff>:/images/images/start.png</normaloff>:/images/images/start.png</iconset>
- </property>
- <property name="iconSize">
- <size>
- <width>43</width>
- <height>20</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCommandLinkButton" name="stop_button">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Stop</string>
- </property>
- <property name="icon">
- <iconset resource="trackmouse-res.qrc">
- <normaloff>:/images/images/stop.png</normaloff>:/images/images/stop.png</iconset>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </widget>
- <resources>
- <include location="trackmouse-res.qrc"/>
- <include location="../../gui/opentrack-res.qrc"/>
- </resources>
- <connections/>
-</ui>
diff --git a/video-opencv/CMakeLists.txt b/video-opencv/CMakeLists.txt
new file mode 100644
index 00000000..0b2460a4
--- /dev/null
+++ b/video-opencv/CMakeLists.txt
@@ -0,0 +1,13 @@
+include(opentrack-opencv)
+find_package(OpenCV QUIET)
+
+if(OpenCV_FOUND)
+ foreach(k core videoio imgcodecs imgproc)
+ otr_install_lib("opencv_${k}" "${opentrack-libexec}")
+ endforeach()
+ otr_module(video-opencv)
+ target_link_libraries(${self} opencv_core opencv_imgcodecs opencv_videoio opentrack-video)
+ if(WIN32)
+ target_link_libraries(${self} strmiids)
+ endif()
+endif()
diff --git a/video-opencv/impl-camera.cpp b/video-opencv/impl-camera.cpp
new file mode 100644
index 00000000..96081399
--- /dev/null
+++ b/video-opencv/impl-camera.cpp
@@ -0,0 +1,104 @@
+#include "impl.hpp"
+#include "compat/sleep.hpp"
+#include "video-property-page.hpp"
+#include <opencv2/core/utils/logger.hpp>
+
+namespace opencv_camera_impl {
+
+cam::cam(int idx) : idx(idx)
+{
+}
+
+cam::~cam()
+{
+ stop();
+}
+
+void cam::stop()
+{
+ if (cap)
+ {
+ if (cap->isOpened())
+ cap->release();
+ cap = std::nullopt;
+ }
+ mat = cv::Mat();
+ frame_ = { {}, false };
+}
+
+bool cam::is_open()
+{
+ return !!cap;
+}
+
+bool cam::start(info& args)
+{
+ stop();
+ cv::utils::logging::setLogLevel(cv::utils::logging::LogLevel::LOG_LEVEL_WARNING);
+ cap.emplace(idx, video_capture_backend);
+
+ if (args.width > 0 && args.height > 0)
+ {
+ cap->set(cv::CAP_PROP_FRAME_WIDTH, args.width);
+ cap->set(cv::CAP_PROP_FRAME_HEIGHT, args.height);
+ }
+ if (args.fps > 0)
+ cap->set(cv::CAP_PROP_FPS, args.fps);
+
+ if (args.use_mjpeg)
+ cap->set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
+
+ if (!cap->isOpened())
+ goto fail;
+
+ if (!get_frame_())
+ goto fail;
+
+ return true;
+
+fail:
+ stop();
+ return false;
+}
+
+bool cam::get_frame_()
+{
+ if (!is_open())
+ return false;
+
+ for (unsigned i = 0; i < 10; i++)
+ {
+ if (cap->read(mat))
+ {
+ frame_.data = mat.data;
+ frame_.width = mat.cols;
+ frame_.height = mat.rows;
+ frame_.stride = mat.step.p[0];
+ if (mat.step.p[0] == (unsigned)frame_.width * mat.elemSize())
+ frame_.stride = cv::Mat::AUTO_STEP;
+
+ frame_.channels = mat.channels();
+
+ return true;
+ }
+ portable::sleep(50);
+ }
+
+ return false;
+}
+
+std::tuple<const frame&, bool> cam::get_frame()
+{
+ bool ret = get_frame_();
+ return { frame_, ret };
+}
+
+bool cam::show_dialog()
+{
+ if (is_open())
+ return video_property_page::show_from_capture(*cap, idx);
+ else
+ return video_property_page::show(idx);
+}
+
+} // ns opencv_camera_impl
diff --git a/video-opencv/impl-metadata.cpp b/video-opencv/impl-metadata.cpp
new file mode 100644
index 00000000..7642c017
--- /dev/null
+++ b/video-opencv/impl-metadata.cpp
@@ -0,0 +1,46 @@
+#include "impl.hpp"
+#include "compat/camera-names.hpp"
+#include "video-property-page.hpp"
+
+namespace opencv_camera_impl {
+
+metadata::metadata() = default;
+
+std::unique_ptr<camera> metadata::make_camera(const QString& name)
+{
+ int idx = camera_name_to_index(name);
+ if (idx != -1)
+ return std::make_unique<cam>(idx);
+ else
+ return nullptr;
+}
+
+std::vector<QString> metadata::camera_names() const
+{
+ std::vector<std::tuple<QString, int>> names = get_camera_names();
+ std::vector<QString> ret;
+ for (const auto& [str, idx] : names)
+ ret.push_back(str);
+ return ret;
+}
+
+bool metadata::can_show_dialog(const QString& camera_name)
+{
+ return camera_name_to_index(camera_name) != -1;
+}
+
+bool metadata::show_dialog(const QString& camera_name)
+{
+ int idx = camera_name_to_index(camera_name);
+ if (idx != -1)
+ {
+ video_property_page::show(idx);
+ return true;
+ }
+ else
+ return false;
+}
+
+OTR_REGISTER_CAMERA(metadata)
+
+} // ns opencv_camera_impl
diff --git a/video-opencv/impl.hpp b/video-opencv/impl.hpp
new file mode 100644
index 00000000..ed5499b0
--- /dev/null
+++ b/video-opencv/impl.hpp
@@ -0,0 +1,55 @@
+/* Copyright (c) 2019 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * 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 "video/camera.hpp"
+#include <optional>
+#include <opencv2/videoio.hpp>
+
+namespace opencv_camera_impl {
+
+using namespace video::impl;
+
+struct metadata : camera_
+{
+ metadata();
+ std::vector<QString> camera_names() const override;
+ std::unique_ptr<camera> make_camera(const QString& name) override;
+ bool can_show_dialog(const QString& camera_name) override;
+ bool show_dialog(const QString& camera_name) override;
+};
+
+struct cam final : camera
+{
+static constexpr int video_capture_backend =
+#ifdef _WIN32
+ cv::CAP_DSHOW;
+#elif !defined __APPLE__
+ cv::CAP_V4L2;
+#else
+ cv::CAP_ANY;
+#endif
+
+ cam(int idx);
+ ~cam() override;
+
+ bool start(info& args) override;
+ void stop() override;
+ bool is_open() override;
+ std::tuple<const frame&, bool> get_frame() override;
+ bool show_dialog() override;
+
+ bool get_frame_();
+
+ std::optional<cv::VideoCapture> cap;
+ cv::Mat mat;
+ frame frame_;
+ int idx = -1;
+};
+
+} // ns opencv_camera_impl
diff --git a/video-opencv/lang/de_DE.ts b/video-opencv/lang/de_DE.ts
new file mode 100644
index 00000000..1552582e
--- /dev/null
+++ b/video-opencv/lang/de_DE.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+</TS>
diff --git a/ext-falcon-bms-linear-acc/lang/nl_NL.ts b/video-opencv/lang/nl_NL.ts
index 9e739505..9e739505 100644
--- a/ext-falcon-bms-linear-acc/lang/nl_NL.ts
+++ b/video-opencv/lang/nl_NL.ts
diff --git a/ext-falcon-bms-linear-acc/lang/ru_RU.ts b/video-opencv/lang/ru_RU.ts
index f62cf2e1..f62cf2e1 100644
--- a/ext-falcon-bms-linear-acc/lang/ru_RU.ts
+++ b/video-opencv/lang/ru_RU.ts
diff --git a/ext-falcon-bms-linear-acc/lang/stub.ts b/video-opencv/lang/stub.ts
index 6401616d..6401616d 100644
--- a/ext-falcon-bms-linear-acc/lang/stub.ts
+++ b/video-opencv/lang/stub.ts
diff --git a/video-opencv/lang/zh_CN.ts b/video-opencv/lang/zh_CN.ts
new file mode 100644
index 00000000..e5ca8aa9
--- /dev/null
+++ b/video-opencv/lang/zh_CN.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+</TS>
diff --git a/cv/video-property-page.cpp b/video-opencv/video-property-page.cpp
index a711e6af..d56d4b91 100644
--- a/cv/video-property-page.cpp
+++ b/video-opencv/video-property-page.cpp
@@ -13,6 +13,8 @@
#include "compat/sleep.hpp"
#include "compat/run-in-thread.hpp"
#include "compat/library-path.hpp"
+#include "compat/thread-name.hpp"
+#include "impl.hpp"
#include <cstring>
@@ -25,11 +27,6 @@
#include <QDebug>
-#include <dshow.h>
-
-#define CHECK(expr) if (FAILED(hr = (expr))) { qDebug() << QLatin1String(#expr) << hr; goto done; }
-#define CHECK2(expr) if (!(expr)) { qDebug() << QLatin1String(#expr); goto done; }
-
bool video_property_page::show_from_capture(cv::VideoCapture& cap, int /*index */)
{
return cap.set(cv::CAP_PROP_SETTINGS, 0);
@@ -37,39 +34,43 @@ bool video_property_page::show_from_capture(cv::VideoCapture& cap, int /*index *
struct prop_settings_worker final : QThread
{
- prop_settings_worker(int idx);
+ explicit prop_settings_worker(int idx);
~prop_settings_worker() override;
- void _open_prop_page();
+
+private:
+ void open_prop_page();
void run() override;
cv::VideoCapture cap;
- int _idx = -1;
+ int idx = -1;
};
-prop_settings_worker::prop_settings_worker(int idx)
+prop_settings_worker::prop_settings_worker(int idx_)
{
- int ret = cap.get(cv::CAP_PROP_SETTINGS);
+ int ret = (int)cap.get(cv::CAP_PROP_SETTINGS);
if (ret != 0)
- run_in_thread_async(qApp, []() {
+ {
+ run_in_thread_async(qApp, [] {
QMessageBox::warning(nullptr,
"Camera properties",
"Camera dialog already opened",
QMessageBox::Cancel,
QMessageBox::NoButton);
});
+ }
else
{
- _idx = idx;
+ idx = idx_;
// DON'T MOVE IT
// ps3 eye will reset to default settings if done from another thread
- _open_prop_page();
+ open_prop_page();
}
}
-void prop_settings_worker::_open_prop_page()
+void prop_settings_worker::open_prop_page()
{
- cap.open(_idx);
+ cap.open(idx + opencv_camera_impl::cam::video_capture_backend);
if (cap.isOpened())
{
@@ -87,18 +88,18 @@ void prop_settings_worker::_open_prop_page()
}
qDebug() << "property-page: can't open camera";
- _idx = -1;
+ idx = -1;
return;
ok:
portable::sleep(100);
- qDebug() << "property-page: opening for" << _idx;
+ qDebug() << "property-page: opening for" << idx;
if (!cap.set(cv::CAP_PROP_SETTINGS, 0))
{
- run_in_thread_async(qApp, []() {
+ run_in_thread_async(qApp, [] {
QMessageBox::warning(nullptr,
"Camera properties",
"Can't open camera dialog",
@@ -110,7 +111,7 @@ ok:
prop_settings_worker::~prop_settings_worker()
{
- if (_idx != -1)
+ if (idx != -1)
{
// ax filter is race condition-prone
portable::sleep(250);
@@ -118,13 +119,15 @@ prop_settings_worker::~prop_settings_worker()
// idem
portable::sleep(250);
- qDebug() << "property-page: closed" << _idx;
+ qDebug() << "property-page: closed" << idx;
}
}
void prop_settings_worker::run()
{
- if (_idx != -1)
+ portable::set_curthread_name("dshow video property page");
+
+ if (idx != -1)
{
while (cap.get(cv::CAP_PROP_SETTINGS) > 0)
portable::sleep(1000);
@@ -137,23 +140,21 @@ bool video_property_page::show(int idx)
// XXX is this a race condition?
thread->moveToThread(qApp->thread());
- QObject::connect(thread, &QThread::finished, qApp, [thread]() { thread->deleteLater(); }, Qt::DirectConnection);
+ QObject::connect(thread, &QThread::finished, qApp, [thread] { thread->deleteLater(); }, Qt::DirectConnection);
thread->start();
return true;
}
-#elif defined(__linux)
+#elif defined(__linux__)
# include <QProcess>
# include "compat/camera-names.hpp"
bool video_property_page::show(int idx)
{
- const QList<QString> camera_names(get_camera_names());
-
- if (idx >= 0 && idx < camera_names.size())
- return QProcess::startDetached("qv4l2", QStringList() << "-d" << camera_names[idx]);
+ if ((unsigned)idx < get_camera_names().size())
+ return QProcess::startDetached("qv4l2", QStringList { "-d", QString("/dev/video%1").arg(idx) });
else
return false;
}
@@ -166,4 +167,3 @@ bool video_property_page::show_from_capture(cv::VideoCapture&, int idx)
bool video_property_page::show(int) { return false; }
bool video_property_page::show_from_capture(cv::VideoCapture&, int) { return false; }
#endif
-
diff --git a/cv/video-property-page.hpp b/video-opencv/video-property-page.hpp
index 4f356b2c..c2b9525d 100644
--- a/cv/video-property-page.hpp
+++ b/video-opencv/video-property-page.hpp
@@ -1,15 +1,8 @@
#pragma once
#include <QString>
-
-#ifdef _WIN32
-# include <windows.h>
-#endif
-
#include <opencv2/videoio.hpp>
-struct IBaseFilter;
-
struct video_property_page final
{
video_property_page() = delete;
@@ -17,3 +10,4 @@ struct video_property_page final
static bool show_from_capture(cv::VideoCapture& cap, int index);
private:
};
+
diff --git a/video-ps3eye/CMakeLists.txt b/video-ps3eye/CMakeLists.txt
new file mode 100644
index 00000000..1f1780f9
--- /dev/null
+++ b/video-ps3eye/CMakeLists.txt
@@ -0,0 +1,69 @@
+add_compile_definitions(PS3EYE_DEBUG)
+
+if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ps3eye-driver/CMakeLists.txt")
+ add_subdirectory("ps3eye-driver")
+
+ if(NOT MSVC)
+ if(PKG_CONFIG_FOUND)
+ pkg_check_modules(libusb "libusb-1.0" QUIET)
+ endif()
+ if(libusb_FOUND)
+ include_directories(SYSTEM ${libusb_INCLUDE_DIRS})
+ link_libraries(${libusb_LIBRARIES})
+ link_directories(${libusb_LIBRARY_DIRS})
+ endif()
+ else()
+ set(SDK_LIBUSB CACHE PATH "")
+ if(SDK_LIBUSB)
+ set(libusb_FOUND TRUE)
+ link_directories("${SDK_LIBUSB}")
+ include_directories(SYSTEM "${SDK_LIBUSB}")
+ endif()
+ endif()
+endif()
+
+if(TARGET ps3eye-sdl AND FALSE)
+ install(TARGETS "ps3eye-sdl" DESTINATION "${opentrack-libexec}")
+ if(WIN32)
+ foreach(k ${SDL2_LIBRARIES})
+ get_filename_component(path "${k}" PATH)
+ set(lib "${path}/SDL2.dll")
+ if(EXISTS "${lib}")
+ otr_install_lib("${lib}" "${opentrack-libexec}")
+ break()
+ endif()
+ endforeach()
+ endif()
+endif()
+
+if(TARGET ps3eye-mode-test)
+ install(TARGETS "ps3eye-mode-test" DESTINATION "${opentrack-libexec}")
+endif()
+
+if(TARGET ps3eye-driver)
+ add_executable(ps3eye-subprocess "wrapper.cxx" "shm.cxx")
+ target_link_libraries(ps3eye-subprocess ps3eye-driver)
+ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
+ target_link_libraries(ps3eye-subprocess rt)
+ endif()
+ install(TARGETS "ps3eye-subprocess" DESTINATION "${opentrack-libexec}")
+endif()
+
+if(TARGET ps3eye-subprocess)
+ otr_module(video-ps3eye)
+ target_link_libraries(${self} ps3eye-driver opentrack-video)
+ if(WIN32)
+ set(path "${SDK_LIBUSB}/libusb-1.0.dll")
+ if(EXISTS "${path}")
+ otr_install_lib("${path}" "${opentrack-libexec}")
+ endif()
+ set(vcrun "${SDK_LIBUSB}/vcruntime140.dll")
+ if(EXISTS "${vcrun}")
+ otr_install_lib("${vcrun}" "${opentrack-libexec}")
+ endif()
+ endif()
+endif()
+
+if(TARGET ps3eye-frame-test)
+ install(TARGETS "ps3eye-frame-test" DESTINATION "${opentrack-libexec}")
+endif()
diff --git a/video-ps3eye/dialog.ui b/video-ps3eye/dialog.ui
new file mode 100644
index 00000000..68060eb9
--- /dev/null
+++ b/video-ps3eye/dialog.ui
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QWidget" name="Dialog">
+ <property name="windowModality">
+ <enum>Qt::ApplicationModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>439</width>
+ <height>124</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>439</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>PS3 Eye</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Camera settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="2">
+ <widget class="QSpinBox" name="exposure_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QSpinBox" name="gain_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="maximum">
+ <number>63</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSlider" name="exposure_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Exposure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSlider" name="gain_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>63</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Gain</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tracker-rift-042/lang/nl_NL.ts b/video-ps3eye/lang/nl_NL.ts
index 9c1a9544..f3a16fd4 100644
--- a/tracker-rift-042/lang/nl_NL.ts
+++ b/video-ps3eye/lang/nl_NL.ts
@@ -2,36 +2,36 @@
<!DOCTYPE TS>
<TS version="2.1" language="nl_NL">
<context>
- <name>dialog_rift_042</name>
+ <name>Dialog</name>
<message>
- <source>Oculus Rift tracker settings FaceTrackNoIR</source>
+ <source>PS3 Eye</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Yaw spring</source>
+ <source>Camera settings</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Enable</source>
+ <source>Exposure</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Persistence</source>
+ <source>Gain</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>dialog</name>
<message>
- <source>Constant drift</source>
+ <source>Can&apos;t open camera</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Deadzone</source>
+ <source>PS3 Eye driver error: %1</source>
<translation type="unfinished"></translation>
</message>
-</context>
-<context>
- <name>rift_tracker_042</name>
<message>
- <source>Unable to start Rift tracker: %1</source>
+ <source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rift-042/lang/ru_RU.ts b/video-ps3eye/lang/ru_RU.ts
index 21068835..1edcf50d 100644
--- a/tracker-rift-042/lang/ru_RU.ts
+++ b/video-ps3eye/lang/ru_RU.ts
@@ -2,36 +2,36 @@
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
<context>
- <name>dialog_rift_042</name>
+ <name>Dialog</name>
<message>
- <source>Oculus Rift tracker settings FaceTrackNoIR</source>
+ <source>PS3 Eye</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Yaw spring</source>
+ <source>Camera settings</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Enable</source>
+ <source>Exposure</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Persistence</source>
+ <source>Gain</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>dialog</name>
<message>
- <source>Constant drift</source>
+ <source>Can&apos;t open camera</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Deadzone</source>
+ <source>PS3 Eye driver error: %1</source>
<translation type="unfinished"></translation>
</message>
-</context>
-<context>
- <name>rift_tracker_042</name>
<message>
- <source>Unable to start Rift tracker: %1</source>
+ <source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/tracker-rift-042/lang/stub.ts b/video-ps3eye/lang/stub.ts
index 91f96f55..81ffc826 100644
--- a/tracker-rift-042/lang/stub.ts
+++ b/video-ps3eye/lang/stub.ts
@@ -2,36 +2,36 @@
<!DOCTYPE TS>
<TS version="2.1">
<context>
- <name>dialog_rift_042</name>
+ <name>Dialog</name>
<message>
- <source>Oculus Rift tracker settings FaceTrackNoIR</source>
+ <source>PS3 Eye</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Yaw spring</source>
+ <source>Camera settings</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Enable</source>
+ <source>Exposure</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Persistence</source>
+ <source>Gain</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>dialog</name>
<message>
- <source>Constant drift</source>
+ <source>Can&apos;t open camera</source>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Deadzone</source>
+ <source>PS3 Eye driver error: %1</source>
<translation type="unfinished"></translation>
</message>
-</context>
-<context>
- <name>rift_tracker_042</name>
<message>
- <source>Unable to start Rift tracker: %1</source>
+ <source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
</context>
diff --git a/video-ps3eye/lang/zh_CN.ts b/video-ps3eye/lang/zh_CN.ts
new file mode 100644
index 00000000..9650c966
--- /dev/null
+++ b/video-ps3eye/lang/zh_CN.ts
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>Dialog</name>
+ <message>
+ <source>PS3 Eye</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation>相机设置</translation>
+ </message>
+ <message>
+ <source>Exposure</source>
+ <translation>æ›å…‰</translation>
+ </message>
+ <message>
+ <source>Gain</source>
+ <translation>增益</translation>
+ </message>
+</context>
+<context>
+ <name>dialog</name>
+ <message>
+ <source>Can&apos;t open camera</source>
+ <translation>无法打开相机</translation>
+ </message>
+ <message>
+ <source>PS3 Eye driver error: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/video-ps3eye/module.cpp b/video-ps3eye/module.cpp
new file mode 100644
index 00000000..02543082
--- /dev/null
+++ b/video-ps3eye/module.cpp
@@ -0,0 +1,307 @@
+#include "module.hpp"
+#include "compat/library-path.hpp"
+#include "compat/sleep.hpp"
+#include "compat/run-in-thread.hpp"
+
+#include <cstddef>
+#include <thread>
+
+#include <QCoreApplication>
+#include <QMessageBox>
+
+#include <libusb.h>
+
+using namespace options;
+
+#ifdef __GNUG__
+# pragma clang diagnostic ignored "-Wcast-qual"
+#endif
+
+int device_count()
+{
+ libusb_context * ctx = nullptr;
+ libusb_device **list = nullptr;
+ ssize_t sz = 0;
+ int rc = 0, cnt = 0;
+
+ constexpr int vendor_id = 0x1415;
+ constexpr int product_id = 0x2000;
+
+ rc = libusb_init(&ctx);
+
+ if (rc)
+ goto end;
+
+ sz = libusb_get_device_list(ctx, &list);
+
+ if (sz < 0)
+ goto end;
+
+ for (int i = 0; i < sz; ++i) {
+ libusb_device *device = list[i];
+ libusb_device_descriptor desc = {};
+
+ if (libusb_get_device_descriptor(device, &desc))
+ goto end;
+
+ if (desc.idVendor == vendor_id && desc.idProduct == product_id)
+ cnt++;
+ }
+
+end:
+ if (list)
+ libusb_free_device_list(list, 1);
+ if (ctx)
+ libusb_exit(ctx);
+
+ return cnt;
+}
+
+#ifdef __linux__
+# include <unistd.h>
+#endif
+
+bool check_device_exists()
+{
+#ifdef __linux__
+ // don't show when system driver exists
+ if (!access("/sys/module/gspca_ov534", R_OK|X_OK))
+ return false;
+#endif
+
+ static bool ret = device_count() > 0;
+ return ret;
+}
+
+static const QString camera_name = QStringLiteral("PS3 Eye open driver");
+
+std::vector<QString> ps3eye_camera_::camera_names() const
+{
+ if (check_device_exists())
+ return { camera_name };
+ else
+ return {};
+}
+std::unique_ptr<camera> ps3eye_camera_::make_camera(const QString& name)
+{
+ if (name == camera_name && check_device_exists())
+ return std::make_unique<ps3eye_camera>();
+ else
+ return {};
+}
+
+static bool show_dialog_()
+{
+ auto& dlg = *new dialog;
+ dlg.setWindowFlag(Qt::MSWindowsFixedSizeDialogHint);
+ dlg.setAttribute(Qt::WA_DeleteOnClose);
+ dlg.adjustSize(); dlg.setFixedSize(dlg.size());
+ dlg.show();
+ return true;
+}
+
+bool ps3eye_camera_::show_dialog(const QString&)
+{
+ return show_dialog_();
+}
+
+bool ps3eye_camera_::can_show_dialog(const QString& name)
+{
+ return name == camera_name && check_device_exists();
+}
+
+ps3eye_camera::ps3eye_camera()
+{
+ if (!shm.success())
+ return;
+
+ static const QString library_path(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH);
+
+ wrapper.setWorkingDirectory(library_path);
+#ifdef _WIN32
+ wrapper.setProgram("\"ps3eye-subprocess.exe\"");
+ // workaround apparent Qt 5.15.2 bug -sh 20210817
+ wrapper.setProcessChannelMode(QProcess::ForwardedChannels);
+#else
+ wrapper.setProgram("ps3eye-subprocess");
+#endif
+}
+
+ps3eye_camera::~ps3eye_camera()
+{
+ stop();
+}
+
+void ps3eye_camera::stop()
+{
+ open = false;
+
+ if (wrapper.state() != QProcess::NotRunning)
+ {
+ volatile auto& ptr = *(ps3eye::shm*)shm.ptr();
+ ptr.in.do_exit = 1;
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ wrapper.waitForFinished(1000);
+
+ if (wrapper.state() != QProcess::NotRunning)
+ wrapper.kill();
+ wrapper.waitForFinished(1000);
+ }
+}
+
+bool ps3eye_camera::start(info& args)
+{
+ if (!shm.success())
+ return false;
+
+ stop();
+
+ volatile auto& ptr = *(ps3eye::shm*)shm.ptr();
+ QString error;
+
+ using mode = ps3eye::shm_in::mode;
+
+ open = false;
+ fr = {};
+ fr.channels = args.num_channels == 1 ? 1 : 3;
+ fr.channel_size = 1;
+
+ if (!args.width || args.width > 320)
+ {
+ ptr.in.resolution = mode::vga;
+ fr.width = 640; fr.height = 480;
+ }
+ else
+ {
+ ptr.in.resolution = mode::qvga;
+ fr.width = 320; fr.height = 240;
+ }
+
+ ptr.in.auto_gain = false;
+ ptr.in.framerate = (uint8_t)std::clamp(args.fps, 30, 187);
+ ptr.in.gain = (uint8_t)s.gain;
+ ptr.in.exposure = (uint8_t)s.exposure;
+ ptr.in.channels = args.num_channels == 1 ? 1 : 3;
+
+ sleep_ms = std::clamp(int(std::floor(450./ptr.in.framerate)), 1, 10);
+
+ wrapper.start();
+
+ constexpr int sleep_ms = 10, max_sleeps = 2000/sleep_ms;
+
+ for (int i = 0; i < max_sleeps; i++)
+ {
+ if (ptr.out.timecode > 0)
+ goto ok;
+ portable::sleep(sleep_ms);
+ }
+
+ if (ptr.out.error_string[0] == '\0')
+ error = QString{};
+ else
+ error = QString::fromLatin1((const char*)ptr.out.error_string,
+ strnlen((const char*)ptr.out.error_string, sizeof(ptr.out.error_string)));
+
+ run_in_thread_async(qApp, [error = std::move(error)] {
+ dialog::show_open_failure_msgbox(error);
+ });
+
+ return false;
+
+ok:
+ open = true;
+ return true;
+}
+
+std::tuple<const frame&, bool> ps3eye_camera::get_frame()
+{
+ auto volatile* ptr = (ps3eye::shm*)shm.ptr();
+
+ if (shm.success() && open)
+ {
+ int elapsed = std::min((int)std::ceil(t.elapsed_ms()), 100);
+ portable::sleep(sleep_ms - elapsed);
+
+ if (unsigned tc = ptr->out.timecode; tc != timecode)
+ {
+ timecode = tc;
+ goto ok;
+ }
+ }
+
+ for (int i = 0; i < 2000; i++)
+ {
+ if (unsigned tc = ptr->out.timecode; tc != timecode)
+ {
+ timecode = tc;
+ goto ok;
+ }
+ portable::sleep(1);
+ }
+
+ stop();
+ return { fr, false };
+
+ static_assert(offsetof(decltype(ptr->out), data_640x480) == offsetof(decltype(ptr->out), data_320x240));
+
+ok:
+ t.start();
+ memcpy(data, (unsigned char*)ptr->out.data_640x480,sizeof(ptr->out.data_640x480));
+ fr.data = data;
+ return { fr, true };
+}
+
+bool ps3eye_camera::show_dialog()
+{
+ return show_dialog_();
+}
+
+OTR_REGISTER_CAMERA(ps3eye_camera_)
+
+dialog::dialog(QWidget* parent) : QWidget(parent)
+{
+ ui.setupUi(this);
+ t.setInterval(500); t.setSingleShot(true);
+ tie_setting(s.exposure, ui.exposure_slider);
+ tie_setting(s.gain, ui.gain_slider);
+ ui.exposure_label->setValue((int)*s.exposure);
+ ui.gain_label->setValue((int)*s.gain);
+ connect(&s.exposure, value_::value_changed<slider_value>(), this, [this](const slider_value&) { t.stop(); t.start(); });
+ connect(&s.gain, value_::value_changed<slider_value>(), this, [this](const slider_value&) { t.stop(); t.start(); });
+ connect(ui.exposure_slider, &QSlider::valueChanged, ui.exposure_label, &QSpinBox::setValue);
+ connect(ui.gain_slider, &QSlider::valueChanged, ui.gain_label, &QSpinBox::setValue);
+ connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &dialog::do_ok);
+ connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &dialog::do_cancel);
+ connect(&t, &QTimer::timeout, this, [this] { s.set_exposure(); s.set_gain(); });
+}
+void dialog::show_open_failure_msgbox(const QString& error)
+{
+ const QString& error_ = error.isNull() ? tr("Unknown error") : error;
+ QMessageBox::critical(nullptr,
+ tr("Can't open camera"),
+ tr("PS3 Eye driver error: %1").arg(error_),
+ QMessageBox::Close);
+}
+
+// XXX copypasta -sh 20200329
+void settings::set_gain()
+{
+ if (!shm.success())
+ return;
+
+ auto& ptr = *(ps3eye::shm volatile*)shm.ptr();
+ ptr.in.gain = (unsigned char)*gain;
+ ++ptr.in.settings_updated;
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+}
+
+void settings::set_exposure()
+{
+ if (!shm.success())
+ return;
+
+ auto& ptr = *(ps3eye::shm volatile*)shm.ptr();
+ ptr.in.exposure = (unsigned char)*exposure;
+ ++ptr.in.settings_updated;
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+}
diff --git a/video-ps3eye/module.hpp b/video-ps3eye/module.hpp
new file mode 100644
index 00000000..f6934d70
--- /dev/null
+++ b/video-ps3eye/module.hpp
@@ -0,0 +1,82 @@
+#pragma once
+
+#include "video/camera.hpp"
+#include "shm-layout.hpp"
+#include "compat/shm.h"
+#include "options/options.hpp"
+#include "compat/macros.h"
+#include "compat/timer.hpp"
+#include "ui_dialog.h"
+
+#include <QDialog>
+#include <QProcess>
+#include <QTimer>
+
+using namespace options;
+
+using video::impl::camera;
+using video::impl::camera_;
+using video::frame;
+
+struct settings final
+{
+ bundle b = make_bundle("video-ps3eye");
+ shm_wrapper shm { "ps3eye-driver-shm", nullptr, sizeof(ps3eye::shm) };
+
+ value<slider_value> exposure{b, "exposure", {255, 0, 255}};
+ value<slider_value> gain{b, "gain", {30, 0, 63}};
+
+ void set_exposure();
+ void set_gain();
+};
+
+class dialog final : public QWidget
+{
+ Q_OBJECT
+ Ui_Dialog ui;
+ settings s;
+ QTimer t{this};
+
+ shm_wrapper shm { "ps3eye-driver-shm", nullptr, sizeof(ps3eye::shm) };
+
+ void do_ok() { s.b->save(); close(); deleteLater(); }
+ void do_cancel() { s.b->reload(); close(); deleteLater(); }
+
+protected:
+ void closeEvent(QCloseEvent*) override { do_cancel(); if (t.isActive()) { s.set_exposure(); s.set_gain(); } }
+
+public:
+ explicit dialog(QWidget* parent = nullptr);
+ static void show_open_failure_msgbox(const QString& error);
+};
+
+struct ps3eye_camera final : video::impl::camera
+{
+ QProcess wrapper;
+ shm_wrapper shm { "ps3eye-driver-shm", nullptr, sizeof(ps3eye::shm) };
+ settings s;
+ frame fr;
+ Timer t;
+ unsigned char data[640 * 480 * ps3eye::num_channels] = {};
+ int framerate = 30, sleep_ms = 1;
+ bool open = false;
+ unsigned timecode = 0;
+
+ ps3eye_camera();
+ ~ps3eye_camera() override;
+
+ bool start(info& args) override;
+ void stop() override;
+ bool is_open() override { return open; }
+
+ std::tuple<const frame&, bool> get_frame() override;
+ [[nodiscard]] bool show_dialog() override;
+};
+
+struct ps3eye_camera_ final : video::impl::camera_
+{
+ std::vector<QString> camera_names() const override;
+ std::unique_ptr<camera> make_camera(const QString& name) override;
+ bool show_dialog(const QString& camera_name) override;
+ bool can_show_dialog(const QString& camera_name) override;
+};
diff --git a/video-ps3eye/ps3eye-driver b/video-ps3eye/ps3eye-driver
new file mode 160000
+Subproject fe4eef71669bc6365a651b3c734ebd2bb5f57f8
diff --git a/video-ps3eye/shm-layout.hpp b/video-ps3eye/shm-layout.hpp
new file mode 100644
index 00000000..65b0a4f1
--- /dev/null
+++ b/video-ps3eye/shm-layout.hpp
@@ -0,0 +1,39 @@
+#pragma once
+#include <cstdint>
+
+namespace ps3eye {
+
+static constexpr unsigned num_channels = 3;
+
+struct shm_in {
+ enum class mode : uint8_t { qvga, vga, };
+
+ uint32_t settings_updated;
+ uint8_t framerate, channels;
+ mode resolution;
+ //uint8_t sharpness, contrast, brightness hue, saturation;
+ uint8_t gain, exposure, auto_gain, test_pattern;
+ uint8_t do_exit;
+};
+
+struct shm_out
+{
+ enum class status : uint8_t { starting, running, fail, terminate, };
+
+ uint32_t timecode;
+ uint32_t settings_updated_ack;
+ status status_;
+ char error_string[256];
+ union {
+ uint8_t data_320x240[320][240][num_channels];
+ uint8_t data_640x480[640][480][num_channels];
+ };
+};
+
+struct alignas(64) shm {
+ shm_out out;
+ [[maybe_unused]] const char _padding[128 - sizeof(shm_out) % 128]; // NOLINT
+ shm_in in;
+};
+
+} // ns ps3eye
diff --git a/video-ps3eye/shm.cxx b/video-ps3eye/shm.cxx
new file mode 100644
index 00000000..57e35c3a
--- /dev/null
+++ b/video-ps3eye/shm.cxx
@@ -0,0 +1,2 @@
+#include "shm.hpp"
+#include "../compat/shm.cpp"
diff --git a/video-ps3eye/shm.hpp b/video-ps3eye/shm.hpp
new file mode 100644
index 00000000..2bb8cb89
--- /dev/null
+++ b/video-ps3eye/shm.hpp
@@ -0,0 +1,4 @@
+#pragma once
+#define OTR_GENERIC_EXPORT
+#define OTR_GENERIC_IMPORT
+#include "../compat/shm.h"
diff --git a/video-ps3eye/wrapper.cxx b/video-ps3eye/wrapper.cxx
new file mode 100644
index 00000000..b7f58185
--- /dev/null
+++ b/video-ps3eye/wrapper.cxx
@@ -0,0 +1,120 @@
+#include "shm-layout.hpp"
+#include "shm.hpp"
+
+#include "ps3eye-driver/ps3eye.hpp"
+
+#include <cstdlib>
+#include <atomic>
+
+#ifdef __clang__
+# pragma clang diagnostic ignored "-Watomic-implicit-seq-cst"
+#endif
+
+#ifdef __GNUG__
+# pragma GCC diagnostic ignored "-Wcast-qual"
+# pragma GCC diagnostic ignored "-Wformat-security"
+# pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
+template<int N, typename... xs>
+[[noreturn]]
+static void error(volatile ps3eye::shm_out& out, const char (&error)[N], const xs&... args)
+{
+ snprintf((char*)out.error_string, sizeof(ps3eye::shm_out::error_string), error, args...);
+ std::quick_exit(2);
+}
+
+static void update_settings(ps3eye::camera& camera, const volatile ps3eye::shm_in& in)
+{
+ //camera.set_framerate(in.framerate);
+ camera.set_auto_gain(in.auto_gain);
+ camera.set_gain(in.gain);
+ camera.set_exposure(in.exposure);
+ camera.set_test_pattern_status(in.test_pattern);
+}
+
+static ps3eye::resolution get_mode(ps3eye::shm_in::mode res)
+{
+ switch (res)
+ {
+ default:
+ case ps3eye::shm_in::mode::qvga:
+ return ps3eye::res_QVGA;
+ case ps3eye::shm_in::mode::vga:
+ return ps3eye::res_VGA;
+ }
+}
+
+int main(int argc, char** argv)
+{
+ (void)argc; (void)argv;
+ shm_wrapper mem_("ps3eye-driver-shm", nullptr, sizeof(ps3eye::shm));
+ volatile auto& ptr_ = *(ps3eye::shm*)mem_.ptr();
+ volatile auto& in = ptr_.in;
+ volatile auto& out = ptr_.out;
+ int num_channels = in.channels;
+
+ auto cameras = ps3eye::list_devices();
+
+ out.status_ = ps3eye::shm_out::status::starting;
+
+ if (cameras.empty())
+ error(out, "no camera found");
+
+ auto& camera = cameras[0];
+ camera->set_debug(true);
+ auto* frame = (uint8_t*)out.data_640x480;
+ decltype(out.timecode) timecode = 0;
+
+ auto fmt = num_channels == 1 ? ps3eye::format::Gray : ps3eye::format::BGR;
+
+ {
+ int framerate = in.framerate;
+ if (framerate <= 0)
+ framerate = 60;
+
+ if (!camera->init(get_mode(in.resolution), framerate, fmt))
+ error(out, "camera init failed: %s", camera->error_string());
+
+ update_settings(*camera, in);
+
+ if (!camera->start())
+ error(out, "can't start camera: %s", camera->error_string());
+ }
+
+ out.timecode = 0;
+ in.do_exit = false;
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+
+ for (;;)
+ {
+ {
+ auto cookie = in.settings_updated;
+ if (cookie != out.settings_updated_ack)
+ {
+ camera->stop();
+ update_settings(*camera, in);
+ int framerate = in.framerate;
+ if (framerate <= 0)
+ framerate = 60;
+ if (!camera->init(get_mode(in.resolution), framerate, fmt))
+ error(out, "camera init failed: %s", camera->error_string());
+ if (!camera->start())
+ error(out, "can't start camera: %s", camera->error_string());
+ out.settings_updated_ack = cookie;
+ }
+ }
+
+ if (!camera->get_frame(frame))
+ continue;
+
+ out.timecode = ++timecode;
+
+ if (in.do_exit)
+ break;
+
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ }
+
+ return 0;
+}
diff --git a/video/CMakeLists.txt b/video/CMakeLists.txt
new file mode 100644
index 00000000..0a9dfd24
--- /dev/null
+++ b/video/CMakeLists.txt
@@ -0,0 +1 @@
+otr_module(video BIN)
diff --git a/video/camera.cpp b/video/camera.cpp
new file mode 100644
index 00000000..a66d8a59
--- /dev/null
+++ b/video/camera.cpp
@@ -0,0 +1,89 @@
+#include "camera.hpp"
+
+#include <algorithm>
+#include <utility>
+#include <QMutex>
+
+std::pair<std::vector<std::unique_ptr<video::impl::camera_>>&, QMutex&> get_metadata()
+{
+ static std::vector<std::unique_ptr<video::impl::camera_>> metadata;
+ static QMutex mtx;
+ return { metadata, mtx };
+}
+
+namespace video::impl {
+
+camera_::camera_() = default;
+camera_::~camera_() = default;
+
+camera::camera() = default;
+camera::~camera() = default;
+
+void register_camera(std::unique_ptr<impl::camera_> camera)
+{
+ auto [metadata, mtx] = get_metadata();
+ QMutexLocker l(&mtx);
+ metadata.push_back(std::move(camera));
+}
+
+} // ns video::impl
+
+namespace video {
+
+bool show_dialog(const QString& camera_name)
+{
+ auto [metadata, mtx] = get_metadata();
+ QMutexLocker l(&mtx);
+
+ for (auto& camera : metadata)
+ for (const QString& name : camera->camera_names())
+ if (name == camera_name)
+ return camera->show_dialog(camera_name);
+
+ return false;
+}
+
+std::unique_ptr<camera_impl> make_camera_(const QString& name)
+{
+ auto [metadata, mtx] = get_metadata();
+ QMutexLocker l(&mtx);
+
+ for (auto& camera : metadata)
+ for (const QString& name_ : camera->camera_names())
+ if (name_ == name)
+ return camera->make_camera(name_);
+
+ return nullptr;
+}
+
+std::unique_ptr<camera_impl> make_camera(const QString& name)
+{
+ if (auto ret = make_camera_(name))
+ return ret;
+
+ auto [metadata, mtx] = get_metadata();
+ for (auto& camera : metadata)
+ for (const QString& name_ : camera->camera_names())
+ if (auto ret = camera->make_camera(name_))
+ return ret;
+
+ return nullptr;
+}
+
+std::vector<QString> camera_names()
+{
+ auto [metadata, mtx] = get_metadata();
+
+ QMutexLocker l(&mtx);
+ std::vector<QString> names; names.reserve(32);
+
+ for (auto& camera : metadata)
+ for (const QString& name : camera->camera_names())
+ if (std::find(names.cbegin(), names.cend(), name) == names.cend())
+ names.push_back(name);
+
+ std::sort(names.begin(), names.end());
+ return names;
+}
+
+} // ns video
diff --git a/video/camera.hpp b/video/camera.hpp
new file mode 100644
index 00000000..6181dbf3
--- /dev/null
+++ b/video/camera.hpp
@@ -0,0 +1,111 @@
+/* Copyright (c) 2019 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * 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 "export.hpp"
+
+#include <memory>
+#include <vector>
+
+#include <QString>
+
+namespace video
+{
+
+struct frame final
+{
+ unsigned char* data = nullptr;
+ // the `stride' member can have a special value of zero,
+ // signifying stride equal to width * element size
+ int width = 0, height = 0, stride = 0, channels = 0, channel_size = 1;
+};
+
+} // ns video
+
+namespace video::impl {
+
+using namespace video;
+
+struct camera;
+
+struct OTR_VIDEO_EXPORT camera_
+{
+ camera_();
+ virtual ~camera_();
+
+ virtual std::vector<QString> camera_names() const = 0;
+ virtual std::unique_ptr<camera> make_camera(const QString& name) = 0;
+ virtual bool show_dialog(const QString& camera_name) = 0;
+ virtual bool can_show_dialog(const QString& camera_name) = 0;
+};
+
+struct OTR_VIDEO_EXPORT camera
+{
+ struct info final
+ {
+ enum : unsigned char { channels_gray = 1, channels_bgr = 3 };
+ // TODO: expose FOV-based focal length for regular webcams
+ int width = 0, height = 0, fps = 0;
+ double fx = 0, fy = 0; // focal length
+ double P_x = 0, P_y = 0; // principal point
+ double dist_c[8] {}; // distortion coefficients
+ bool use_mjpeg = false;
+ int num_channels = channels_bgr;
+ };
+
+ camera();
+ virtual ~camera();
+
+ [[nodiscard]] virtual bool start(info& args) = 0;
+ virtual void stop() = 0;
+ virtual bool is_open() = 0;
+
+ virtual std::tuple<const frame&, bool> get_frame() = 0;
+ [[nodiscard]] virtual bool show_dialog() = 0;
+};
+
+OTR_VIDEO_EXPORT
+void register_camera(std::unique_ptr<impl::camera_> metadata);
+
+} // ns video::impl
+
+#define OTR_REGISTER_CAMERA3(type, ctr) \
+ static const char init_ ## ctr = \
+ (::video::impl::register_camera(std::make_unique<type>()), 0);
+
+#ifdef _MSC_VER
+ // shared library targets without any symbols break cmake build
+# define OTR_REGISTER_CAMERA_IMPL(type) \
+ extern "C" [[maybe_unused]] __declspec(dllexport) \
+ void _opentrack_module_video_ ##type (void) {}
+# define OTR_REGISTER_CAMERA_IMPL2(type) \
+ OTR_REGISTER_CAMERA_IMPL(type)
+#else
+# define OTR_REGISTER_CAMERA_IMPL2(type)
+#endif
+
+#define OTR_REGISTER_CAMERA2(type, ctr) \
+ OTR_REGISTER_CAMERA3(type, ctr) \
+ OTR_REGISTER_CAMERA_IMPL2(type)
+#define OTR_REGISTER_CAMERA(type) \
+ OTR_REGISTER_CAMERA2(type, __COUNTER__)
+namespace video
+{
+using camera_impl = impl::camera;
+
+OTR_VIDEO_EXPORT std::unique_ptr<camera_impl> make_camera(const QString& name);
+OTR_VIDEO_EXPORT std::unique_ptr<camera_impl> make_camera_(const QString& name);
+
+OTR_VIDEO_EXPORT
+std::vector<QString> camera_names();
+
+[[nodiscard]]
+OTR_VIDEO_EXPORT
+bool show_dialog(const QString& camera_name);
+
+} // ns video
diff --git a/video/export.hpp b/video/export.hpp
new file mode 100644
index 00000000..fc850193
--- /dev/null
+++ b/video/export.hpp
@@ -0,0 +1,11 @@
+// generates export.hpp for each module from compat/linkage.hpp
+
+#pragma once
+
+#include "compat/linkage-macros.hpp"
+
+#ifdef BUILD_VIDEO
+# define OTR_VIDEO_EXPORT OTR_GENERIC_EXPORT
+#else
+# define OTR_VIDEO_EXPORT OTR_GENERIC_IMPORT
+#endif
diff --git a/video/lang/de_DE.ts b/video/lang/de_DE.ts
new file mode 100644
index 00000000..1552582e
--- /dev/null
+++ b/video/lang/de_DE.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+</TS>
diff --git a/variant/trackmouse/lang/nl_NL.ts b/video/lang/nl_NL.ts
index 9e739505..9e739505 100644
--- a/variant/trackmouse/lang/nl_NL.ts
+++ b/video/lang/nl_NL.ts
diff --git a/variant/trackmouse/lang/ru_RU.ts b/video/lang/ru_RU.ts
index f62cf2e1..f62cf2e1 100644
--- a/variant/trackmouse/lang/ru_RU.ts
+++ b/video/lang/ru_RU.ts
diff --git a/variant/trackmouse/lang/stub.ts b/video/lang/stub.ts
index 6401616d..6401616d 100644
--- a/variant/trackmouse/lang/stub.ts
+++ b/video/lang/stub.ts
diff --git a/video/lang/zh_CN.ts b/video/lang/zh_CN.ts
new file mode 100644
index 00000000..e5ca8aa9
--- /dev/null
+++ b/video/lang/zh_CN.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+</TS>
diff --git a/video/video-widget.cpp b/video/video-widget.cpp
new file mode 100644
index 00000000..3018a3c4
--- /dev/null
+++ b/video/video-widget.cpp
@@ -0,0 +1,90 @@
+#include "video-widget.hpp"
+
+#include "compat/check-visible.hpp"
+#include "compat/math.hpp"
+
+#include <cstring>
+
+#include <QPainter>
+#include <QtAlgorithms>
+#include <QDebug>
+
+void video_widget::init_image_nolock()
+{
+ double dpi = devicePixelRatioF();
+ size_.store({ iround(width() * dpi), iround(height() * dpi) }, std::memory_order_release);
+}
+
+video_widget::video_widget(QWidget* parent) : QWidget(parent)
+{
+ if (parent)
+ setFixedSize(parent->size());
+ else
+ setFixedSize(320, 240);
+ init_image_nolock();
+ connect(&timer, &QTimer::timeout, this, &video_widget::draw_image, Qt::DirectConnection);
+ timer.start(15);
+}
+
+void video_widget::update_image(const QImage& img)
+{
+ if (fresh())
+ return;
+
+ set_image(img.constBits(), img.width(), img.height(),
+ img.bytesPerLine(), img.format());
+ set_fresh(true);
+}
+
+void video_widget::set_image(const unsigned char* src, int width, int height, int stride, QImage::Format fmt)
+{
+ QMutexLocker l(&mtx);
+
+ texture = QImage();
+ unsigned nbytes = (unsigned)(stride * height);
+ vec.resize(nbytes); vec.shrink_to_fit();
+ std::memcpy(vec.data(), src, nbytes);
+ texture = QImage((const unsigned char*)vec.data(), width, height, stride, fmt);
+}
+
+void video_widget::paintEvent(QPaintEvent*)
+{
+ QPainter painter(this);
+
+ QMutexLocker l(&mtx);
+ painter.drawImage(rect(), texture);
+}
+
+void video_widget::draw_image()
+{
+ if (!fresh())
+ return;
+
+ if (!check_is_visible())
+ return;
+
+ repaint();
+ set_fresh(false);
+}
+
+void video_widget::resizeEvent(QResizeEvent*)
+{
+ QMutexLocker l(&mtx);
+ init_image_nolock();
+}
+
+std::tuple<int, int> video_widget::preview_size() const
+{
+ QSize sz = size_.load(std::memory_order_acquire);
+ return { sz.width(), sz.height() };
+}
+
+bool video_widget::fresh() const
+{
+ return fresh_.load(std::memory_order_acquire);
+}
+
+void video_widget::set_fresh(bool x)
+{
+ fresh_.store(x, std::memory_order_release);
+}
diff --git a/video/video-widget.hpp b/video/video-widget.hpp
new file mode 100644
index 00000000..4f54b2b9
--- /dev/null
+++ b/video/video-widget.hpp
@@ -0,0 +1,49 @@
+/* Copyright (c) 2014-2016, 2019 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * 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 "compat/math.hpp"
+#include "export.hpp"
+
+#include <vector>
+#include <atomic>
+#include <tuple>
+
+#include <QWidget>
+#include <QImage>
+#include <QTimer>
+
+#include <QMutex>
+
+struct OTR_VIDEO_EXPORT video_widget : QWidget
+{
+ video_widget(QWidget* parent = nullptr);
+
+ void update_image(const QImage& image);
+ std::tuple<int, int> preview_size() const;
+ void resizeEvent(QResizeEvent*) override;
+ void paintEvent(QPaintEvent*) override;
+ void draw_image();
+ bool fresh() const;
+
+protected:
+ mutable QMutex mtx { QMutex::NonRecursive };
+ QImage texture;
+ std::vector<unsigned char> vec;
+ void set_fresh(bool x);
+ void set_image(const unsigned char* src, int width, int height, int stride, QImage::Format fmt);
+
+private:
+ void init_image_nolock();
+ QTimer timer;
+
+ std::atomic<QSize> size_ = QSize(320, 240);
+ std::atomic<bool> fresh_ { false };
+
+ static_assert(decltype(fresh_)::is_always_lock_free);
+};
diff --git a/x-plane-plugin/CMakeLists.txt b/x-plane-plugin/CMakeLists.txt
index 32ec16e2..8e8fb338 100644
--- a/x-plane-plugin/CMakeLists.txt
+++ b/x-plane-plugin/CMakeLists.txt
@@ -1,36 +1,46 @@
if(LINUX OR APPLE)
- set(SDK_XPLANE "" CACHE PATH "Path to X-Plane SDK")
+ set(SDK_XPLANE "" CACHE PATH "Path to the X-Plane SDK")
if(SDK_XPLANE)
- otr_module(xplane-plugin NO-QT)
+ if (APPLE)
+ otr_module(xplane-plugin NO-QT NO-INSTALL)
+ else()
+ otr_module(xplane-plugin NO-QT)
+ endif()
# probably librt already included
- install(FILES ${opentrack-xplane-plugin-c} DESTINATION "${opentrack-doc-src-pfx}/opentrack-xplane-plugin")
- target_include_directories(opentrack-xplane-plugin SYSTEM PUBLIC ${SDK_XPLANE}/CHeaders ${SDK_XPLANE}/CHeaders/XPLM)
+ #install(FILES ${opentrack-xplane-plugin-c} DESTINATION "opentrack-libexec")
+ target_include_directories(opentrack-xplane-plugin SYSTEM PUBLIC ${SDK_XPLANE}/CHeaders/XPLM)
if(APPLE)
- set_property(TARGET opentrack-xplane-plugin APPEND_STRING PROPERTY
- COMPILE_FLAGS "-iframework ${SDK_XPLANE}/Libraries/Mac/ -DAPL -DXPLM200 -DXPLM210 -framework XPLM -framework XPWidgets ")
- set_property(TARGET opentrack-xplane-plugin APPEND_STRING PROPERTY
- LINK_FLAGS "-F${SDK_XPLANE}/Libraries/Mac/ -framework XPLM -framework XPWidgets ")
+ target_compile_options(${self} PRIVATE
+ -iframework "${SDK_XPLANE}/Libraries/Mac/"
+ -DAPL -DXPLM200 -DXPLM210
+ -framework XPLM)
+ target_link_options(${self} PRIVATE
+ "-F${SDK_XPLANE}/Libraries/Mac/"
+ -framework XPLM)
+
+ install(TARGETS "${self}"
+ RUNTIME DESTINATION ${opentrack-bin}/xplane
+ BUNDLE DESTINATION ${opentrack-bin}/xplane
+ LIBRARY DESTINATION ${opentrack-bin}/xplane
+ PERMISSIONS ${opentrack-perms-exec})
+
elseif(CMAKE_COMPILER_IS_GNUCXX)
- set_property(TARGET opentrack-xplane-plugin APPEND_STRING PROPERTY
- COMPILE_FLAGS "-fPIC -DLIN -DXPLM200 -DXPLM210 ")
- set_property(TARGET opentrack-xplane-plugin APPEND_STRING PROPERTY
- LINK_FLAGS "-rdynamic -nodefaultlibs -fPIC ")
+ target_compile_options(${self} PRIVATE -DLIN -DXPLM200 -DXPLM210)
+ target_link_options(${self} PRIVATE -rdynamic -nodefaultlibs)
endif()
- if(CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_COMPILER_IS_CLANG)
- set_property(TARGET opentrack-xplane-plugin APPEND_STRING PROPERTY
- LINK_FLAGS "-undefined_warning ")
+ if(CMAKE_COMPILER_IS_GNUCC AND NOT CMAKE_COMPILER_IS_CLANG)
+ target_link_options(${self} PRIVATE -undefined_warning)
endif()
- set_target_properties(opentrack-xplane-plugin PROPERTIES
+ set_target_properties(${self} PROPERTIES
LIBRARY_OUTPUT_NAME "opentrack.xpl"
- PREFIX ""
- SUFFIX "")
+ PREFIX "" SUFFIX "")
if(UNIX AND NOT APPLE)
- target_link_libraries(opentrack-xplane-plugin rt)
+ target_link_libraries(${self} rt)
endif()
endif()
endif()
diff --git a/x-plane-plugin/plugin.c b/x-plane-plugin/plugin.c
index 1c41daf0..e43ee0ef 100644
--- a/x-plane-plugin/plugin.c
+++ b/x-plane-plugin/plugin.c
@@ -31,8 +31,17 @@
#define WINE_SHM_NAME "facetracknoir-wine-shm"
#define WINE_MTX_NAME "facetracknoir-wine-mtx"
-#define BUILD_compat
-#include "compat/export.hpp"
+#include "compat/linkage-macros.hpp"
+
+#ifndef MAP_FAILED
+# define MAP_FAILED ((void*)-1)
+#endif
+
+#ifdef __GNUC__
+# pragma GCC diagnostic ignored "-Wimplicit-float-conversion"
+# pragma GCC diagnostic ignored "-Wdouble-promotion"
+# pragma GCC diagnostic ignored "-Wlanguage-extension-token"
+#endif
enum Axis {
TX = 0, TY, TZ, Yaw, Pitch, Roll
@@ -50,7 +59,7 @@ typedef struct WineSHM
int gameid, gameid2;
unsigned char table[8];
bool stop;
-} WineSHM;
+} volatile WineSHM;
static shm_wrapper* lck_posix = NULL;
static WineSHM* shm_posix = NULL;
@@ -100,11 +109,10 @@ void shm_wrapper_unlock(shm_wrapper* self)
flock(self->fd, LOCK_UN);
}
-float write_head_position(
- float inElapsedSinceLastCall,
- float inElapsedTimeSinceLastFlightLoop,
- int inCounter,
- void * inRefcon )
+float write_head_position(float inElapsedSinceLastCall,
+ float inElapsedTimeSinceLastFlightLoop,
+ int inCounter,
+ void* inRefcon)
{
if (lck_posix != NULL && shm_posix != NULL) {
shm_wrapper_lock(lck_posix);
@@ -122,17 +130,19 @@ float write_head_position(
return -1.0;
}
-static int TrackToggleHandler( XPLMCommandRef inCommand,
- XPLMCommandPhase inPhase,
- void * inRefCon )
+static int TrackToggleHandler(XPLMCommandRef inCommand,
+ XPLMCommandPhase inPhase,
+ void* inRefCon)
{
- if ( track_disabled )
+ if (inPhase != xplm_CommandBegin) return 0;
+
+ if (track_disabled)
{
//Enable
- XPLMRegisterFlightLoopCallback(write_head_position, -1.0, NULL);
+ XPLMRegisterFlightLoopCallback(write_head_position, -1, NULL);
// Reinit the offsets when we re-enable the plugin
- if ( !translation_disabled )
+ if (!translation_disabled)
reinit_offset();
}
else
@@ -144,10 +154,12 @@ static int TrackToggleHandler( XPLMCommandRef inCommand,
return 0;
}
-static int TranslationToggleHandler( XPLMCommandRef inCommand,
- XPLMCommandPhase inPhase,
- void * inRefCon )
+static int TranslationToggleHandler(XPLMCommandRef inCommand,
+ XPLMCommandPhase inPhase,
+ void* inRefCon)
{
+ if (inPhase != xplm_CommandBegin) return 0;
+
translation_disabled = !translation_disabled;
if (!translation_disabled)
{
@@ -157,36 +169,53 @@ static int TranslationToggleHandler( XPLMCommandRef inCommand,
return 0;
}
-PLUGIN_API OTR_COMPAT_EXPORT int XPluginStart ( char * outName, char * outSignature, char * outDescription ) {
- view_x = XPLMFindDataRef("sim/aircraft/view/acf_peX");
- view_y = XPLMFindDataRef("sim/aircraft/view/acf_peY");
- view_z = XPLMFindDataRef("sim/aircraft/view/acf_peZ");
+static inline
+void volatile_explicit_bzero(void volatile* restrict ptr, size_t len)
+{
+ for (size_t i = 0; i < len; i++)
+ *((char volatile* restrict)ptr + i) = 0;
+
+ asm volatile("" ::: "memory");
+}
+
+PLUGIN_API OTR_GENERIC_EXPORT
+int XPluginStart (char* outName, char* outSignature, char* outDescription) {
+ // view_x = XPLMFindDataRef("sim/aircraft/view/acf_peX");
+ // view_y = XPLMFindDataRef("sim/aircraft/view/acf_peY");
+ // view_z = XPLMFindDataRef("sim/aircraft/view/acf_peZ");
+ // view_heading = XPLMFindDataRef("sim/graphics/view/pilots_head_psi");
+ // view_pitch = XPLMFindDataRef("sim/graphics/view/pilots_head_the");
+ // view_roll = XPLMFindDataRef("sim/graphics/view/field_of_view_roll_deg");
+
+ view_x = XPLMFindDataRef("sim/graphics/view/pilots_head_x");
+ view_y = XPLMFindDataRef("sim/graphics/view/pilots_head_y");
+ view_z = XPLMFindDataRef("sim/graphics/view/pilots_head_z");
view_heading = XPLMFindDataRef("sim/graphics/view/pilots_head_psi");
view_pitch = XPLMFindDataRef("sim/graphics/view/pilots_head_the");
- view_roll = XPLMFindDataRef("sim/graphics/view/field_of_view_roll_deg");
+ view_roll = XPLMFindDataRef("sim/graphics/view/pilots_head_phi");
track_toggle = XPLMCreateCommand("opentrack/toggle", "Disable/Enable head tracking");
translation_disable_toggle = XPLMCreateCommand("opentrack/toggle_translation", "Disable/Enable input translation from opentrack");
- XPLMRegisterCommandHandler( track_toggle,
- TrackToggleHandler,
- 1,
- (void*)0);
+ XPLMRegisterCommandHandler(track_toggle,
+ TrackToggleHandler,
+ 1,
+ NULL);
- XPLMRegisterCommandHandler( translation_disable_toggle,
- TranslationToggleHandler,
- 1,
- (void*)0);
+ XPLMRegisterCommandHandler(translation_disable_toggle,
+ TranslationToggleHandler,
+ 1,
+ NULL);
if (view_x && view_y && view_z && view_heading && view_pitch && track_toggle && translation_disable_toggle) {
lck_posix = shm_wrapper_init(WINE_SHM_NAME, WINE_MTX_NAME, sizeof(WineSHM));
- if (lck_posix->mem == (void*)-1) {
+ if (lck_posix->mem == MAP_FAILED) {
fprintf(stderr, "opentrack failed to init SHM!\n");
return 0;
}
- shm_posix = (WineSHM*) lck_posix->mem;
- memset(shm_posix, 0, sizeof(WineSHM));
+ shm_posix = lck_posix->mem;
+ volatile_explicit_bzero(shm_posix, sizeof(WineSHM));
strcpy(outName, "opentrack");
strcpy(outSignature, "opentrack - freetrack lives!");
strcpy(outDescription, "head tracking view control");
@@ -196,7 +225,8 @@ PLUGIN_API OTR_COMPAT_EXPORT int XPluginStart ( char * outName, char * outSignat
return 0;
}
-PLUGIN_API OTR_COMPAT_EXPORT void XPluginStop ( void ) {
+PLUGIN_API OTR_GENERIC_EXPORT
+void XPluginStop (void) {
if (lck_posix)
{
shm_wrapper_free(lck_posix);
@@ -205,21 +235,23 @@ PLUGIN_API OTR_COMPAT_EXPORT void XPluginStop ( void ) {
}
}
-PLUGIN_API OTR_COMPAT_EXPORT int XPluginEnable ( void ) {
+PLUGIN_API OTR_GENERIC_EXPORT
+int XPluginEnable (void) {
XPLMRegisterFlightLoopCallback(write_head_position, -1.0, NULL);
track_disabled = 0;
return 1;
}
-PLUGIN_API OTR_COMPAT_EXPORT void XPluginDisable ( void ) {
+PLUGIN_API OTR_GENERIC_EXPORT
+void XPluginDisable (void) {
XPLMUnregisterFlightLoopCallback(write_head_position, NULL);
track_disabled = 1;
}
-PLUGIN_API OTR_COMPAT_EXPORT void XPluginReceiveMessage(
- XPLMPluginID inFromWho,
- int inMessage,
- void * inParam)
+PLUGIN_API OTR_GENERIC_EXPORT
+void XPluginReceiveMessage(XPLMPluginID inFromWho,
+ int inMessage,
+ void * inParam)
{
if (inMessage == XPLM_MSG_AIRPORT_LOADED)
reinit_offset();