diff options
581 files changed, 27507 insertions, 10450 deletions
diff --git a/.clang-format b/.clang-format index 3cc08007..eb080eb9 100644 --- a/.clang-format +++ b/.clang-format @@ -1,8 +1,9 @@ BasedOnStyle: LLVM AccessModifierOffset: -4 AlignEscapedNewlines: Right +AlignOperands: Align AllowAllParametersOfDeclarationOnNextLine: false -AllowShortBlocksOnASingleLine: false +AllowShortBlocksOnASingleLine: Never AllowShortFunctionsOnASingleLine: InlineOnly AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false @@ -13,7 +14,7 @@ BreakBeforeBinaryOperators: NonAssignment BreakBeforeBraces: Allman BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false -ColumnLimit: 100 +ColumnLimit: 150 ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 @@ -41,6 +42,6 @@ SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false -Standard: Cpp11 +Standard: Latest TabWidth: 4 UseTab: Never diff --git a/.clang-tidy b/.clang-tidy index 89ad1d49..df2a6e10 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,61 +1,107 @@ -Checks: - #-*, - *, - clang-analyzer-*,clang-diagnostic-*, - cppcoreguidelines-*,performance-*,bugprone-*, - readability-*,misc-*,hicpp-*,cert-*,modernize-*, - -clang-diagnostic-nonportable-include-path, - -clang-analyzer-deadcode.DeadStores, - -clang-analyze-core.CallAndMessage, - -cppcoreguidelines-special-member-functions,-hicpp-special-member-functions, - -hicpp-braces-around-statements,-readability-braces-around-statements, - -hicpp-use-equals-default -modernize-use-equals-default, - -hicpp-use-auto, - -hicpp-vararg, - -hicpp-member-init, - -cert-err58-cpp, - -readability-implicit-bool-conversion, - -readability-else-after-return, - -modernize-raw-string-literal, - -hicpp-no-array-decay,-cppcoreguidelines-pro-bounds-array-to-pointer-decay, - -cert-err34-c, - -hicpp-signed-bitwise, - -modernize-use-auto,-hicpp-use-auto, - -readability-named-parameter, - -cert-dcl54-cpp, - -hicpp-new-delete-operators, - -modernize-pass-by-value, - -cppcoreguidelines-pro-type-union-access, - -hicpp-use-equals-default, - -cppcoreguidelines-pro-bounds-pointer-arithmetic, - -cppcoreguidelines-pro-bounds-constant-array-index, - -cppcoreguidelines-pro-bounds-array-to-pointer-decay, - -cppcoreguidelines-pro-type-vararg, - -cppcoreguidelines-pro-type-cstyle-cast, - -cppcoreguidelines-pro-type-reinterpret-cast, - -cppcoreguidelines-owning-memory, - -cppcoreguidelines-pro-type-const-cast, - -hicpp-function-size, - -cppcoreguidelines-pro-type-static-cast-downcast, - -hicpp-use-override, - -hicpp-use-equals-delete, - -readability-static-accessed-through-instance, - -cert-flp30-c, - -modernize-redundant-void-arg, - -hicpp-explicit-conversions, - -readability-function-size, - -cppcoreguidelines-avoid-goto, - -modernize-use-using, - -cppcoreguidelines-c-copy-assignment-signature, - -hicpp-avoid-goto, - -readability-inconsistent-declaration-parameter-name, - -cppcoreguidelines-avoid-magic-numbers, - -readability-magic-numbers, - -modernize-loop-convert, - -modernize-use-nodiscard, +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 -#HeaderFilterRegex: '/dev/opentrack' CheckOptions: - key: cert-dcl59-cpp.HeaderFileExtensions @@ -78,5 +124,11 @@ CheckOptions: 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/.gitattributes b/.gitattributes index 05e51885..462acc88 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,4 +19,4 @@ *.in text eol=lf *.cmd text eol=crlf -*.bat text eol=crlf
\ No newline at end of file +*.bat text eol=crlf 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}} @@ -7,3 +7,5 @@ /.vs/ /CMakeSettings.json .gtm/ +.vscode +.history 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/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/CMakeLists.txt b/CMakeLists.txt index a7a95d2f..c28ead74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,8 +29,8 @@ set(CMAKE_CXX_LINKER_PREFERENCE_PROPAGATES ON) list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") include(opentrack-policy NO_POLICY_SCOPE) -project(opentrack) 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) @@ -66,7 +66,7 @@ include(opentrack-mrproper) set_property(GLOBAL PROPERTY opentrack-all-modules "") set_property(GLOBAL PROPERTY opentrack-all-source-dirs "") -set(opentrack_all-translations "nl_NL;ru_RU;stub;zh_CN") +set(opentrack_all-translations "de_DE;nl_NL;ru_RU;stub;zh_CN") include(opentrack-hier) include(opentrack-platform) @@ -87,41 +87,49 @@ add_custom_target(mrproper 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() include(opentrack-install) -otr_install_sources() +message("Install directory:") +message(" ${CMAKE_INSTALL_PREFIX}") string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE) -message(STATUS "-- Compile flags:") +message("Compile flags:") #foreach(j C CXX) foreach(j CXX) foreach(i "" "_${CMAKE_BUILD_TYPE}") - message(STATUS " ${j}${i}: ${CMAKE_${j}_FLAGS${i}}") + message(" ${j}${i}: ${CMAKE_${j}_FLAGS${i}}") endforeach() endforeach() -message(STATUS "-- Link flags:") +message("Link flags:") foreach(j "" "_${CMAKE_BUILD_TYPE}") #foreach(i EXE SHARED) foreach(i SHARED) - message(STATUS " LINK_${i}${j}: ${CMAKE_${i}_LINKER_FLAGS${j}}") + message(" LINK_${i}${j}: ${CMAKE_${i}_LINKER_FLAGS${j}}") endforeach() endforeach() -message(STATUS "-- Static archive flags:") +message("Static archive flags:") foreach(k "" "_${CMAKE_BUILD_TYPE}") - message(STATUS " STATIC${k}: ${CMAKE_STATIC_LINKER_FLAGS${k}}") + message(" STATIC${k}: ${CMAKE_STATIC_LINKER_FLAGS${k}}") endforeach() -message(STATUS "--") +message("--") @@ -1,8 +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 -[<img src="https://ci.appveyor.com/api/projects/status/n0j9h38jnif5qbe9/branch/unstable?svg=true"/>](https://ci.appveyor.com/project/sthalik/opentrack/branch/unstable) +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>>. -opentrack project home is located at <<http://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) @@ -10,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 @@ -26,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 @@ -48,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) @@ -57,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) @@ -68,15 +68,19 @@ Don't be afraid to submit an **issue/feature request** if you have any problems! - 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) @@ -84,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/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/zh_CN.ts b/api/lang/zh_CN.ts index d29cce1b..26bf67a5 100644 --- a/api/lang/zh_CN.ts +++ b/api/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>module_status_mixin</name> <message> diff --git a/api/plugin-api.cpp b/api/plugin-api.cpp index 79f9b464..4d8d90e9 100644 --- a/api/plugin-api.cpp +++ b/api/plugin-api.cpp @@ -1,28 +1,28 @@ #include "plugin-api.hpp" - -#include <utility> +#include <QCloseEvent> +#include <QDebug> namespace plugin_api::detail { BaseDialog::BaseDialog() = default; -void BaseDialog::closeEvent(QCloseEvent*) +void BaseDialog::closeEvent(QCloseEvent* e) { if (isVisible()) - { - hide(); emit closing(); - } + e->accept(); } void BaseDialog::done(int) { if (isVisible()) - { - hide(); close(); - } } +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. @@ -36,10 +36,14 @@ 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; } @@ -47,14 +51,11 @@ ITrackerDialog::ITrackerDialog() = default; ITrackerDialog::~ITrackerDialog() = default; void ITrackerDialog::register_tracker(ITracker*) {} void ITrackerDialog::unregister_tracker() {} -IExtension::~IExtension() = default; -IExtensionDialog::~IExtensionDialog() = default; 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() = default; diff --git a/api/plugin-api.hpp b/api/plugin-api.hpp index 263ee475..2d77bdf4 100644 --- a/api/plugin-api.hpp +++ b/api/plugin-api.hpp @@ -42,6 +42,10 @@ 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: @@ -55,15 +59,18 @@ private slots: #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(void) \ + OTR_PLUGIN_EXPORT ctor_ret_class* GetConstructor(void); \ + ctor_ret_class* GetConstructor(void) \ { \ return new ctor_class; \ } \ - OTR_PLUGIN_EXPORT Metadata_* GetMetadata(void) \ + OTR_PLUGIN_EXPORT Metadata_* GetMetadata(void); \ + Metadata_* GetMetadata(void) \ { \ return new metadata_class; \ } \ - OTR_PLUGIN_EXPORT dialog_ret_class* GetDialog(void) \ + OTR_PLUGIN_EXPORT dialog_ret_class* GetDialog(void); \ + dialog_ret_class* GetDialog(void) \ { \ return new dialog_class; \ } \ @@ -221,48 +228,3 @@ struct OTR_API_EXPORT ITrackerDialog : public plugin_api::detail::BaseDialog #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; - ~IExtension() override; - - 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& 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 9c0a3ae0..4300da18 100644 --- a/api/plugin-support.hpp +++ b/api/plugin-support.hpp @@ -38,7 +38,6 @@ enum class dylib_type : unsigned Filter = 0xdeadbabe, Tracker = 0xcafebeef, Protocol = 0xdeadf00d, - Extension = 0xcafebabe, Video = 0xbadf00d, Invalid = (unsigned)-1, }; @@ -54,7 +53,7 @@ struct dylib final return; handle.setFileName(filename_); - handle.setLoadHints(QLibrary::DeepBindHint | QLibrary::PreventUnloadHint | QLibrary::ResolveAllSymbolsHint); + handle.setLoadHints(QLibrary::DeepBindHint | QLibrary::ResolveAllSymbolsHint); #ifdef __clang__ # pragma clang diagnostic push @@ -154,7 +153,6 @@ private: OPENTRACK_LIBRARY_PREFIX "opentrack-tracker-", OPENTRACK_LIBRARY_PREFIX "opentrack-proto-", OPENTRACK_LIBRARY_PREFIX "opentrack-filter-", - OPENTRACK_LIBRARY_PREFIX "opentrack-ext-", OPENTRACK_LIBRARY_PREFIX "opentrack-video-", }; @@ -180,20 +178,17 @@ struct Modules final filter_modules(filter(type::Filter)), tracker_modules(filter(type::Tracker)), protocol_modules(filter(type::Protocol)), - extension_modules(filter(type::Extension)), 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) @@ -229,7 +224,6 @@ private: { 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::Extension, OPENTRACK_LIBRARY_PREFIX "opentrack-ext-*." OPENTRACK_LIBRARY_EXTENSION, }, { type::Video, OPENTRACK_LIBRARY_PREFIX "opentrack-video-*." OPENTRACK_LIBRARY_EXTENSION, dylib_load_none, }, }; diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 2da7f2fd..00000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,24 +0,0 @@ -# Starter pipeline -# Start with a minimal pipeline that you can customize to build and deploy your code. -# Add steps that build, run tests, deploy, and more: -# https://aka.ms/yaml - -trigger: -- unstable - -pool: - vmImage: 'vs2017-win2016' - -steps: -- script: echo Hello, world! - displayName: 'Run a one-line script' - -- script: | - echo Add other tasks to build, test, and deploy your project. - echo See https://aka.ms/yaml - set - displayName: 'Run a multi-line script' - -- task: CMake@1 - inputs: - cmakeArgs:
\ No newline at end of file diff --git a/cmake/FindEigen3.cmake b/cmake/FindEigen3.cmake deleted file mode 100644 index b43208f9..00000000 --- a/cmake/FindEigen3.cmake +++ /dev/null @@ -1,90 +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=1 - -DEIGEN_MAX_CPP_VER=17 - -DEIGEN_DEFAULT_DENSE_INDEX_TYPE=int - -DEIGEN_NO_DEBUG=1 - -DEIGEN_DONT_PARALLELIZE=1 - ) -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/apple.cmake b/cmake/apple.cmake index f6b83382..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_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/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 b7a403a3..b390c120 100644 --- a/cmake/msvc.cmake +++ b/cmake/msvc.cmake @@ -4,6 +4,23 @@ SET(CMAKE_SYSTEM_NAME Windows) 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,13 +28,44 @@ SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + #add_definitions(-D_ITERATOR_DEBUG_LEVEL=0) -add_definitions(-diagnostics:caret) #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() + +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) + +set(CMAKE_C_EXTENSIONS FALSE) +set(CMAKE_CXX_EXTENSIONS FALSE) + +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() + 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") - include("${CMAKE_CURRENT_LIST_DIR}/opentrack-policy.cmake" NO_POLICY_SCOPE) + #include("${CMAKE_CURRENT_LIST_DIR}/opentrack-policy.cmake" NO_POLICY_SCOPE) add_compile_options("-W4") @@ -30,31 +78,98 @@ if(CMAKE_PROJECT_NAME STREQUAL "opentrack") endforeach() endif() -if(CMAKE_PROJECT_NAME STREQUAL "opencv") +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(FEATURE_debug OFF) + set(FEATURE_debug_and_release OFF) + set(FEATURE_force_debug_info ON) + set(FEATURE_ltcg ON) + set(FEATURE_shared ON) +endif() + +if(CMAKE_PROJECT_NAME STREQUAL "OpenCV") + set(PARALLEL_ENABLE_PLUGINS FALSE) + set(HIGHGUI_ENABLE_PLUGINS FALSE) + set(VIDEOIO_ENABLE_PLUGINS FALSE) + set(OPENCV_SKIP_MSVC_EXCEPTIONS_FLAG TRUE) + set(OPENCV_DISABLE_THREAD_SUPPORT TRUE) + + 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() + +if(CMAKE_PROJECT_NAME STREQUAL "TestOscpack") + add_compile_definitions(OSC_HOST_LITTLE_ENDIAN) +endif() + +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() -add_compile_options(-Zi -Zf -Zo -bigobj -cgthreads1 -vd0) +if(opentrack-64bit) + set(opentrack-simd "AVX") +endif() + +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL") +add_compile_options(-MD) + add_link_options(-cgthreads:1) -set(_CFLAGS "") -set(_CXXFLAGS "") -set(_CFLAGS_RELEASE "-O2 -O2it -Oy- -Ob3 -fp:fast -GS- -GF -GL -Gw -Gy -arch:SSE2 -GR- -MT") +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(_LDFLAGS_RELEASE "-OPT:REF -OPT:ICF=10 -LTCG:INCREMENTAL -DEBUG:FULL") +set(_LDFLAGS "-WX") +set(_LDFLAGS_RELEASE "-OPT:REF,ICF=10 -LTCG -DEBUG:FULL") set(_LDFLAGS_DEBUG "-DEBUG:FULL") -set(_LDFLAGS_STATIC "") -set(_LDFLAGS_STATIC_RELEASE "-LTCG:INCREMENTAL") +set(_LDFLAGS_STATIC "-WX") +set(_LDFLAGS_STATIC_RELEASE "-LTCG") set(_LDFLAGS_STATIC_DEBUG "") -if(NOT CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE STRING "" FORCE) -endif() - set(CMAKE_BUILD_TYPE_INIT "RELEASE" CACHE INTERNAL "") string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE) diff --git a/cmake/opentrack-boilerplate.cmake b/cmake/opentrack-boilerplate.cmake index ffa4ea02..e325cd6a 100644 --- a/cmake/opentrack-boilerplate.cmake +++ b/cmake/opentrack-boilerplate.cmake @@ -18,10 +18,10 @@ set(new-hier-path "#pragma once #endif #define OPENTRACK_LIBRARY_PREFIX \"\" -#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}\" +#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}\" ") function(otr_write_library_paths) @@ -44,15 +44,15 @@ function(otr_glob_sources var) endforeach() foreach(dir ${ARGN}) set(dir "${basedir}/${dir}") - file(GLOB cxx "${dir}/*.cpp") - file(GLOB cc "${dir}/*.c") - file(GLOB hh "${dir}/*.h" "${dir}/*.hpp" "${dir}/*.inc") - file(GLOB res "${dir}/*.rc") + 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 ui "${dir}/*.ui") - file(GLOB rc "${dir}/*.qrc") + 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}}" ${${i}} PARENT_SCOPE) @@ -61,8 +61,8 @@ function(otr_glob_sources var) 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") @@ -84,7 +84,7 @@ function(otr_compat target) endif() get_property(type TARGET "${n}" PROPERTY TYPE) - if (".${TYPE}" STREQUAL ".EXECUTABLE") + if (type STREQUAL "EXECUTABLE") otr_fixup_subsystem(${target}) endif() endfunction() @@ -109,7 +109,7 @@ 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() @@ -143,15 +143,17 @@ function(otr_module n_) set(arg_NO-I18N TRUE) endif() - if(NOT WIN32) - set(subsys "") - elseif(arg_WIN32-CONSOLE) - set(subsys "") - else() - set(subsys "WIN32") - 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}" @@ -175,7 +177,7 @@ function(otr_module n_) set(self "${n}" PARENT_SCOPE) - if(NOT arg_RELINK) + 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) @@ -192,12 +194,7 @@ function(otr_module n_) string(REPLACE "-" "_" build-n ${n_}) string(TOUPPER "${build-n}" build-n) target_compile_definitions(${n} PRIVATE "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() - target_compile_definitions(${n} PRIVATE OPENTRACK_ORG=\"${ident}\") + include(opentrack-org) if(arg_STATIC) set(arg_NO-INSTALL TRUE) @@ -216,13 +213,26 @@ function(otr_module n_) endif() if(NOT arg_NO-INSTALL) + # Librarys/executable if(arg_BIN) - install(TARGETS "${n}" - RUNTIME DESTINATION ${opentrack-hier-bin} - LIBRARY DESTINATION ${opentrack-hier-pfx} + 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} + # Plugins + install(TARGETS "${n}" + RUNTIME DESTINATION "${opentrack-libexec}" + LIBRARY DESTINATION "${opentrack-libexec}" PERMISSIONS ${opentrack-perms-exec}) endif() @@ -250,7 +260,7 @@ 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() @@ -282,7 +292,7 @@ function(otr_install_lib target dest) set(pdb-path "") otr_pdb_for_dll(pdb-path "${path}") if(pdb-path) - install(FILES "${pdb-path}" DESTINATION "${opentrack-hier-debug}" PERMISSIONS ${opentrack-perms-exec}) + install(FILES "${pdb-path}" DESTINATION "${opentrack-debug}" PERMISSIONS ${opentrack-perms-exec}) endif() endif() install(FILES "${path}" DESTINATION "${dest}" PERMISSIONS ${opentrack-perms-exec}) diff --git a/cmake/opentrack-hier.cmake b/cmake/opentrack-hier.cmake index 7dcdb52d..bb1a435d 100644 --- a/cmake/opentrack-hier.cmake +++ b/cmake/opentrack-hier.cmake @@ -9,50 +9,45 @@ include_guard(GLOBAL) -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-bin ".") - set(opentrack-doc-pfx "./doc") - set(opentrack-doc-src-pfx "./source-code") - set(opentrack-i18n-pfx "./i18n") - set(opentrack-i18n-path "./i18n") + 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-hier-pfx "modules") - set(opentrack-hier-path "/${opentrack-hier-pfx}/") # MUST HAVE A TRAILING BACKSLASH - set(opentrack-hier-doc "/doc/") # MUST HAVE A TRAILING BACKSLASH - set(opentrack-hier-bin ".") - set(opentrack-doc-pfx "./doc") - set(opentrack-doc-src-pfx "./source-code") - set(opentrack-i18n-pfx "./i18n") - set(opentrack-i18n-path "./i18n") - set(opentrack-hier-debug "./debug") + 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-hier-pfx "libexec/opentrack") - set(opentrack-hier-path "/../${opentrack-hier-pfx}/") # MUST HAVE A TRAILING BACKSLASH - set(opentrack-hier-doc "/share/doc/opentrack/") # MUST HAVE A TRAILING BACKSLASH - set(opentrack-hier-bin "bin") - 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-i18n-pfx "./share/opentrack/i18n") - set(opentrack-i18n-path "../share/opentrack/i18n") + 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() -set(opentrack-hier-str RUNTIME DESTINATION ${opentrack-hier-pfx} LIBRARY DESTINATION ${opentrack-hier-pfx}) 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-pfx}/contrib") +set(opentrack-contrib-pfx "${opentrack-doc}/contrib") set(opentrack-binary-suffix "") -if(APPLE) - set(opentrack-binary-suffix ".bin") -elseif(WIN32) +if(WIN32) set(opentrack-binary-suffix ".exe") endif() diff --git a/cmake/opentrack-i18n.cmake b/cmake/opentrack-i18n.cmake index e89f5c42..1f0c67d9 100644 --- a/cmake/opentrack-i18n.cmake +++ b/cmake/opentrack-i18n.cmake @@ -1,9 +1,7 @@ include_guard(GLOBAL) add_custom_target(i18n-lupdate) -set_property(TARGET i18n-lupdate PROPERTY FOLDER "i18n") add_custom_target(i18n-lrelease DEPENDS i18n-lupdate) -set_property(TARGET i18n-lrelease PROPERTY FOLDER "i18n") add_custom_target(i18n ALL DEPENDS i18n-lrelease) function(otr_i18n_for_target_directory n) @@ -15,7 +13,7 @@ function(otr_i18n_for_target_directory n) set(ts-files "") foreach(k ${opentrack_all-translations}) - list(APPEND ts-files "lang/${k}.ts") + 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") @@ -26,6 +24,7 @@ function(otr_i18n_for_target_directory n) 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() @@ -48,18 +47,19 @@ function(otr_i18n_for_target_directory n) -ts ${ts-files} ${to-null} COMMAND "${CMAKE_COMMAND}" -E touch "${stamp}" - DEPENDS ${${k}-cc} ${${k}-hh} ${${k}-uih} ${${k}-moc} + #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 "") - set_property(TARGET ${target-name} PROPERTY FOLDER "i18n") add_dependencies(i18n-lupdate ${target-name}) endfunction() function(otr_merge_translations) - otr_escape_string(i18n-pfx "${opentrack-i18n-pfx}") - install(CODE "file(REMOVE_RECURSE \"\${CMAKE_INSTALL_PREFIX}/${i18n-pfx}\")") + 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}") @@ -68,7 +68,7 @@ function(otr_merge_translations) set(qm-output "${CMAKE_BINARY_DIR}/${i}.qm") # whines about duplicate messages since tracker-pt-base is static - if(WIN32) + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") set(to-null "2>NUL") else() set(to-null "2>/dev/null") @@ -82,17 +82,16 @@ function(otr_merge_translations) ${ts-files} -qm "${qm-output}" ${to-null} - DEPENDS ${ts-files} i18n-lupdate + 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}") - set_property(TARGET ${target-name} PROPERTY FOLDER "i18n") 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() endfunction() diff --git a/cmake/opentrack-install.cmake b/cmake/opentrack-install.cmake index 2b745a82..5aac983b 100644 --- a/cmake/opentrack-install.cmake +++ b/cmake/opentrack-install.cmake @@ -16,53 +16,28 @@ macro(otr_install_dir path) ) endmacro() -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() - -function(otr_install_sources) - 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_install_dir("${opentrack-doc-src-pfx}" "${dest}") - endforeach() - - otr_install_dir("${opentrack-doc-src-pfx}" "${CMAKE_SOURCE_DIR}/cmake") - otr_install_dir("${opentrack-doc-src-pfx}" "${CMAKE_SOURCE_DIR}/bin") - - otr_install_misc("${opentrack-doc-src-pfx}" FILES "${CMAKE_SOURCE_DIR}/CMakeLists.txt") - otr_install_misc("${opentrack-doc-pfx}" FILES "${CMAKE_SOURCE_DIR}/README.md") - otr_install_misc("${opentrack-doc-pfx}" FILES "${CMAKE_SOURCE_DIR}/.github/CONTRIBUTING.md") - otr_install_misc("${opentrack-doc-pfx}" FILES "${CMAKE_SOURCE_DIR}/WARRANTY.txt") - otr_install_misc("${opentrack-doc-pfx}" FILES "${CMAKE_SOURCE_DIR}/OPENTRACK-LICENSING.txt") - otr_install_misc("${opentrack-doc-pfx}" FILES "${CMAKE_SOURCE_DIR}/AUTHORS.md") -endfunction() - function(cleanup_visual_studio_debug) otr_escape_string(pfx "${CMAKE_INSTALL_PREFIX}") install(CODE "file(REMOVE_RECURSE \"${pfx}/.vs\")") endfunction() -otr_install_dir("${opentrack-doc-pfx}" ${CMAKE_SOURCE_DIR}/3rdparty-notices) -otr_install_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_install_misc(. FILES "${CMAKE_SOURCE_DIR}/bin/qt.conf") otr_install_misc(. FILES "${CMAKE_SOURCE_DIR}/bin/cleye.config") - otr_install_misc(${opentrack-hier-pfx} FILES "${CMAKE_SOURCE_DIR}/bin/cleye.config") + otr_install_misc(${opentrack-libexec} FILES "${CMAKE_SOURCE_DIR}/bin/cleye.config") endif() -otr_install_misc("${opentrack-doc-pfx}" FILES ${CMAKE_SOURCE_DIR}/README.md) +otr_install_misc("${opentrack-doc}" FILES ${CMAKE_SOURCE_DIR}/README.md) -otr_install_misc("${opentrack-doc-pfx}" FILES "${CMAKE_SOURCE_DIR}/README.md") -otr_install_misc("${opentrack-doc-pfx}" FILES "${CMAKE_SOURCE_DIR}/.github/CONTRIBUTING.md") -otr_install_misc("${opentrack-doc-pfx}" FILES "${CMAKE_SOURCE_DIR}/WARRANTY.txt") -otr_install_misc("${opentrack-doc-pfx}" FILES "${CMAKE_SOURCE_DIR}/OPENTRACK-LICENSING.txt") -otr_install_misc("${opentrack-doc-pfx}" FILES "${CMAKE_SOURCE_DIR}/AUTHORS.md") +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 @@ -71,9 +46,13 @@ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") cleanup_visual_studio_debug() endif() -otr_install_exec("${opentrack-hier-pfx}" FILES "${CMAKE_SOURCE_DIR}/bin/freetrackclient.dll") -otr_install_exec("${opentrack-hier-pfx}" FILES "${CMAKE_SOURCE_DIR}/bin/freetrackclient64.dll") -otr_install_exec("${opentrack-hier-pfx}" FILES +# 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") diff --git a/cmake/opentrack-load-user-settings.cmake b/cmake/opentrack-load-user-settings.cmake index 1baf3702..fdb63936 100644 --- a/cmake/opentrack-load-user-settings.cmake +++ b/cmake/opentrack-load-user-settings.cmake @@ -26,12 +26,11 @@ else() set(__sdk_host_os "") endif() -set(__sdk_paths_basename "sdk-paths-${__sdk_username}@${CMAKE_CXX_COMPILER_ID}-${__sdk_host_os}${__sdk_target_os}.cmake") -set(__sdk_paths_filename "${CMAKE_SOURCE_DIR}/${__sdk_paths_basename}") +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_basename}'") + message(STATUS "Loading user settings '${__sdk_paths_filename}'") include("${__sdk_paths_filename}") else() - message(STATUS "User settings file '${__sdk_paths_basename}' doesn't exist") + message(STATUS "User settings file '${__sdk_paths_filename}' doesn't exist") endif() diff --git a/cmake/opentrack-opencv.cmake b/cmake/opentrack-opencv.cmake index b8c3701c..3acadf4a 100644 --- a/cmake/opentrack-opencv.cmake +++ b/cmake/opentrack-opencv.cmake @@ -2,13 +2,3 @@ include_guard(GLOBAL) include(opentrack-boilerplate) find_package(OpenCV QUIET) - -function(otr_install_opencv_libs) - foreach(k core features2d calib3d flann imgcodecs imgproc videoio) - otr_install_lib("opencv_${k}" "${opentrack-hier-pfx}") - endforeach() -endfunction() - -if(TARGET opencv_core) - otr_install_opencv_libs() -endif() 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 index 5b84cf40..0f13defc 100644 --- a/cmake/opentrack-pkg-config.cmake +++ b/cmake/opentrack-pkg-config.cmake @@ -1,7 +1,8 @@ include_guard(GLOBAL) include(FindPkgConfig) -function(otr_pkgconfig target) +function(otr_pkgconfig_ ret target) + set(${ret} 1 PARENT_SCOPE) foreach(i ${ARGN}) set(k pkg-config_${i}) pkg_check_modules(${k} QUIET ${i}) @@ -11,6 +12,17 @@ function(otr_pkgconfig target) 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() diff --git a/cmake/opentrack-platform.cmake b/cmake/opentrack-platform.cmake index 02c1b2f8..638260f1 100644 --- a/cmake/opentrack-platform.cmake +++ b/cmake/opentrack-platform.cmake @@ -34,12 +34,10 @@ set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "" FORCE) include_directories("${CMAKE_SOURCE_DIR}") -set(opentrack_maintainer-mode FALSE CACHE INTERNAL "Select if developing core code (not modules)") - set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_DEFAULT 17) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_DEFAULT 20) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(CMAKE_CXX_EXTENSIONS FALSE) @@ -54,7 +52,6 @@ set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) set(CMAKE_C_VISIBILITY_PRESET hidden) set(CMAKE_CXX_VISIBILITY_PRESET hidden) -set(CMAKE_MACOSX_RPATH OFF) if(NOT WIN32 AND NOT APPLE) include(opentrack-pkg-config) @@ -98,6 +95,7 @@ if(CMAKE_COMPILER_IS_GNUCXX AND NOT APPLE) 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() endif() @@ -119,15 +117,15 @@ if(MSVC) add_definitions(-D_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1) add_definitions(-D_SCL_SECURE_NO_WARNINGS) - add_compile_options(-EHs-c-) + #add_compile_options(-EHsc) add_definitions(-D_HAS_EXCEPTIONS=0) 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) - add_compile_options(-diagnostics:caret) add_compile_options(-permissive-) if(opentrack-64bit) @@ -139,6 +137,16 @@ if(MSVC) 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 @@ -147,22 +155,15 @@ if(MSVC) #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) + 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(APPLE) - add_compile_definitions(-stdlib=libc++) - add_link_options(-stdlib=libc++) - - add_link_options(-framework Cocoa -framework CoreFoundation -framework Carbon) - link_libraries(objc z) -endif() - if(NOT MSVC) include(FindPkgConfig) endif() diff --git a/cmake/opentrack-policy.cmake b/cmake/opentrack-policy.cmake index 209ef529..5fd8647f 100644 --- a/cmake/opentrack-policy.cmake +++ b/cmake/opentrack-policy.cmake @@ -13,9 +13,12 @@ set(_policies 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 8c92483c..1735e836 100644 --- a/cmake/opentrack-qt.cmake +++ b/cmake/opentrack-qt.cmake @@ -2,22 +2,38 @@ include_guard(GLOBAL) if(WIN32) find_package(Qt5Gui REQUIRED COMPONENTS QWindowsIntegrationPlugin) endif() -find_package(Qt5 REQUIRED COMPONENTS Core Network Widgets LinguistTools Gui QUIET) -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() + +find_package(Qt5 REQUIRED COMPONENTS ${qt-required-components} QUIET) +find_package(Qt5 COMPONENTS ${qt-optional-components} QUIET) 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) - foreach(i Qt5::Core Qt5::Gui Qt5::Network Qt5::SerialPort Qt5::Widgets) + 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 ".") @@ -35,7 +51,7 @@ function(otr_qt n) endfunction() function(otr_qt2 n) - target_include_directories("${n}" PRIVATE SYSTEM + target_include_directories("${n}" SYSTEM PRIVATE ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS} ) target_compile_definitions("${n}" PRIVATE diff --git a/cmake/opentrack-rift.cmake b/cmake/opentrack-rift.cmake deleted file mode 100644 index cdcfc946..00000000 --- a/cmake/opentrack-rift.cmake +++ /dev/null @@ -1,38 +0,0 @@ -include_guard(GLOBAL) -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 2cf39fcd..5cb4bd57 100644 --- a/cmake/opentrack-variant.cmake +++ b/cmake/opentrack-variant.cmake @@ -1,29 +1,32 @@ include_guard(GLOBAL) -function(otr_dist_select_variant) - file(GLOB variants "variant/*") +function(otr_init_variant) + set_property(GLOBAL PROPERTY opentrack-variant "default") + set_property(GLOBAL PROPERTY opentrack-ident "opentrack-2.3") - set(variant-list "") - - foreach(k ${variants}) - get_filename_component(name "${k}" NAME) - if(EXISTS "${k}/_variant.cmake") - list(APPEND variant-list "${name}") - else() - message(FATAL_ERROR "stray 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") - 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() + 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/translation-stub.ts b/cmake/translation-stub.ts index 6401616d..9d616636 100644 --- a/cmake/translation-stub.ts +++ b/cmake/translation-stub.ts @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<TS version="2.1" language="@LANG@"> </TS> diff --git a/compat/CMakeLists.txt b/compat/CMakeLists.txt index fb498fb6..dc81436d 100644 --- a/compat/CMakeLists.txt +++ b/compat/CMakeLists.txt @@ -1,7 +1,3 @@ -if(CMAKE_COMPILER_IS_GNUCXX) - add_compile_options(-fno-lto -fno-fast-math -fno-finite-math-only -O0) -endif() - otr_module(compat NO-COMPAT BIN) if(NOT WIN32 AND NOT APPLE) diff --git a/compat/camera-names.cpp b/compat/camera-names.cpp index 69926e5a..b9511037 100644 --- a/compat/camera-names.cpp +++ b/compat/camera-names.cpp @@ -7,11 +7,15 @@ # include <cwchar> # define NO_DSHOW_STRSAFE # include <dshow.h> -#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> @@ -24,16 +28,52 @@ int camera_name_to_index(const QString &name) { auto list = get_camera_names(); - auto it = std::find(list.cbegin(), list.cend(), name); + 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()) - return std::distance(list.cbegin(), it); + { + const auto& [ str, idx ] = *it; + return idx; + } return -1; } -std::vector<QString> get_camera_names() +#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; +} + +static QString device_path_from_qstring(const QString& str) { - std::vector<QString> ret; + // 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() +{ + std::vector<std::tuple<QString, int>> ret; #ifdef _WIN32 // Create the System Device Enumerator. HRESULT hr; @@ -57,31 +97,31 @@ std::vector<QString> get_camera_names() { IPropertyBag *pPropBag; hr = pMoniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void **)&pPropBag); - if (SUCCEEDED(hr)) { + if (SUCCEEDED(hr)) { // To retrieve the filter's friendly name, do the following: - VARIANT var; - VariantInit(&var); - hr = pPropBag->Read(L"FriendlyName", &var, nullptr); if (SUCCEEDED(hr)) { - // Display the name in your UI somehow. - QString str((QChar*)var.bstrVal, int(std::wcslen(var.bstrVal))); - ret.push_back(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(&var); 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[32]; snprintf(buf, sizeof(buf), "/dev/video%d", i); @@ -97,10 +137,16 @@ std::vector<QString> get_camera_names() close(fd); continue; } - ret.push_back(QString((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 bda15e81..3585edfe 100644 --- a/compat/camera-names.hpp +++ b/compat/camera-names.hpp @@ -10,9 +10,10 @@ #include <vector> #include <QString> +# include <tuple> #include "export.hpp" -OTR_COMPAT_EXPORT std::vector<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 f786ca14..621d941a 100644 --- a/compat/check-visible.cpp +++ b/compat/check-visible.cpp @@ -10,7 +10,7 @@ static bool visible = true; #if defined _WIN32 #include "timer.hpp" -#include "macros.hpp" +#include "macros.h" static Timer timer; @@ -29,6 +29,15 @@ void set_is_visible(const QWidget& w, bool force) return; } + { + int ndisplays = GetSystemMetrics(SM_CMONITORS); + if (ndisplays > 1) + { + visible = true; + return; + } + } + HWND hwnd = (HWND)w.winId(); if (!force && timer.elapsed_ms() < (visible ? visible_timeout : invisible_timeout)) diff --git a/compat/check-visible.hpp b/compat/check-visible.hpp index e24260ab..6669b84d 100644 --- a/compat/check-visible.hpp +++ b/compat/check-visible.hpp @@ -1,7 +1,7 @@ #pragma once #include "export.hpp" -#include "macros1.h" +#include "macros.h" class QWidget; diff --git a/compat/correlation-calibrator.cpp b/compat/correlation-calibrator.cpp index 01f3b14f..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> @@ -49,7 +50,7 @@ bool correlation_calibrator::check_buckets(const vec6& data) for (unsigned k = 0; k < 6; k++) { - const double val = clamp(data[k], min[k], max[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]) 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/ext-falcon-bms-linear-acc/lang/nl_NL.ts b/compat/lang/de_DE.ts index 9e739505..1552582e 100644 --- a/ext-falcon-bms-linear-acc/lang/nl_NL.ts +++ b/compat/lang/de_DE.ts @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1" language="nl_NL"> +<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/linkage-macros.hpp b/compat/linkage-macros.hpp index 64c64bdb..f4d0104b 100644 --- a/compat/linkage-macros.hpp +++ b/compat/linkage-macros.hpp @@ -1,14 +1,16 @@ #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__ diff --git a/compat/macros1.h b/compat/macros.h index 2ebfc4df..7de7fb86 100644 --- a/compat/macros1.h +++ b/compat/macros.h @@ -9,7 +9,7 @@ #if defined _MSC_VER # define force_inline __forceinline #else -# define force_inline __attribute__((always_inline)) +# define force_inline __attribute__((always_inline)) inline #endif #if !defined likely @@ -38,5 +38,20 @@ # 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); }()) -# define eval_once(expr) eval_once2(expr, __COUNTER__) +# 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 497933cf..00000000 --- a/compat/macros.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include "macros1.h" - -#include <utility> -#include <type_traits> - -// before C++20 -namespace cxx20_compat { - template<typename T> - struct remove_cvref { - using type = std::remove_cv_t<std::remove_reference_t<T>>; - }; -} // ns cxx20_compat - -template<typename t> -using remove_cvref_t = typename cxx20_compat::remove_cvref<t>::type; - -template<typename t> -using to_const_ref_t = std::add_lvalue_reference_t<std::add_const_t<remove_cvref_t<t>>>; - -template<typename t> -using cv_qualified = std::conditional_t<std::is_fundamental_v<remove_cvref_t<t>>, - remove_cvref_t<t>, - to_const_ref_t<t>>; diff --git a/compat/math.hpp b/compat/math.hpp index 34428cbd..c5981cc2 100644 --- a/compat/math.hpp +++ b/compat/math.hpp @@ -1,72 +1,15 @@ #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> -struct clamp final -{ - static inline auto clamp_(t val, t min_, t max_) - { - if (unlikely(val > max_)) - return max_; - if (unlikely(val < min_)) - return min_; - return val; - } -}; - -template<> -struct clamp<float> -{ - static inline auto clamp_(float val, float min_, float max_) - { - return clamp_float(val, min_, max_); - } -}; - -template<> -struct clamp<double> -{ - 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 v> -inline auto clamp(const t& val, const u& min, const v& max) -{ - using w = cv_qualified<decltype(val + min + max)>; - return util_detail::clamp<w>::clamp_(val, min, max); -} - template<typename t> -inline auto iround(t val) -> std::enable_if_t<std::is_floating_point_v<remove_cvref_t<t>>, int> +inline auto iround(t val) -> std::enable_if_t<std::is_floating_point_v<std::decay_t<t>>, int> { return (int)std::round(val); } -template<typename t> -inline auto uround(t val) -> std::enable_if_t<std::is_floating_point_v<remove_cvref_t<t>>, unsigned> -{ - return (unsigned)std::fmax(0, std::round(val)); -} - template <typename t> -force_inline constexpr int signum(const t& x) { return x < t{0} ? -1 : 1; diff --git a/compat/process-list.hpp b/compat/process-list.hpp index d1f9999f..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) @@ -129,15 +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> QStringList get_all_executable_names() { - QStringList ret; + QStringList ret; ret.reserve(2048); proc_t** procs = readproctab(PROC_FILLCOM); if (procs == nullptr) { @@ -160,6 +213,7 @@ 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 index 01659c8a..fc17a062 100644 --- a/compat/qt-dpi.hpp +++ b/compat/qt-dpi.hpp @@ -3,17 +3,22 @@ #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 { - const auto& x = *static_cast<const self*>(this); -#ifdef _WIN32 - return std::max(x.devicePixelRatioF(), 1.); -#else - return std::max(std::max(x.logicalDpiX()/(double)x.physicalDpiX(), x.devicePixelRatioF()), 1.); -#endif + return ::screen_dpi(static_cast<const self*>(this)); } }; diff --git a/compat/qt-signal.cpp b/compat/qt-signal.cpp index 08aac663..8c23866b 100644 --- a/compat/qt-signal.cpp +++ b/compat/qt-signal.cpp @@ -1,13 +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 { -namespace qt_sig { +sig_void::sig_void(QObject* parent) : QObject(parent) {} -nullary::nullary(QObject* parent) : QObject(parent) {} -nullary::~nullary() = default; - -void nullary::operator()() const -{ - notify(); } - -} // ns qt_sig diff --git a/compat/qt-signal.hpp b/compat/qt-signal.hpp index 119e063c..f74de642 100644 --- a/compat/qt-signal.hpp +++ b/compat/qt-signal.hpp @@ -3,28 +3,70 @@ // 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 { +namespace _qt_sig_impl { -class OTR_COMPAT_EXPORT nullary : public QObject +template<typename t> struct sig; + +class OTR_COMPAT_EXPORT sig_void final : public QObject { Q_OBJECT public: template<typename t, typename F> - nullary(t* datum, F&& f, Qt::ConnectionType conntype = Qt::AutoConnection) : QObject(datum) + sig_void(t* datum, F&& f, Qt::ConnectionType conntype = Qt::AutoConnection) : QObject(datum) { - connect(this, &nullary::notify, datum, f, conntype); + connect(this, &sig_void::notify, datum, f, conntype); } - - nullary(QObject* parent = nullptr); - ~nullary() override; - - void operator()() const; + explicit sig_void(QObject* parent = nullptr); + void operator()() const { notify(); } signals: void notify() const; }; -} // ns qt_sig +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 b8ffc179..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(); - if (!t) abort(); - 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.h b/compat/shm.h index d1363219..5036b335 100644 --- a/compat/shm.h +++ b/compat/shm.h @@ -20,7 +20,7 @@ #endif #include "export.hpp" -#include "macros1.h" +#include "macros.h" class OTR_COMPAT_EXPORT shm_wrapper final { @@ -39,4 +39,6 @@ public: bool unlock(); bool success(); inline void* ptr() { return mem; } + + OTR_DISABLE_MOVE_COPY(shm_wrapper); }; diff --git a/compat/thread-name.cpp b/compat/thread-name.cpp index 504c6f19..08a7d628 100644 --- a/compat/thread-name.cpp +++ b/compat/thread-name.cpp @@ -20,12 +20,9 @@ struct THREADNAME_INFO }; static inline -void set_curthread_name_old(const QString& name_) +void set_curthread_name_old_(const char* name) { - QByteArray str = name_.toLocal8Bit(); - const char* name = str.constData(); HANDLE curthread = GetCurrentThread(); - THREADNAME_INFO info; // NOLINT(cppcoreguidelines-pro-type-member-init) info.dwType = 0x1000; info.szName = name; @@ -41,6 +38,15 @@ void set_curthread_name_old(const QString& name_) { } } + +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&) {} diff --git a/compat/timer.cpp b/compat/timer.cpp index f60e4f62..fdea185a 100644 --- a/compat/timer.cpp +++ b/compat/timer.cpp @@ -23,26 +23,24 @@ void Timer::start() gettime(&state); } -struct timespec Timer::gettime_() const +struct timespec Timer::get_delta() const { struct timespec ts; // NOLINT gettime(&ts); - unsigned long long a = ts.tv_sec, b = state.tv_sec; - int c = ts.tv_nsec, d = state.tv_nsec; - return { (time_t)(a - b), (long)(c - d) }; + return { ts.tv_sec - state.tv_sec, ts.tv_nsec - state.tv_nsec }; } // milliseconds double Timer::elapsed_ms() const { - struct timespec delta = gettime_(); + struct timespec delta = get_delta(); return delta.tv_sec * 1000 + delta.tv_nsec * 1e-6; } double Timer::elapsed_seconds() const { - struct timespec delta = gettime_(); + struct timespec delta = get_delta(); return delta.tv_sec + delta.tv_nsec * 1e-9; } @@ -63,20 +61,14 @@ static auto otr_get_clock_frequency() void Timer::gettime(timespec* state) { - static const unsigned long long freq = otr_get_clock_frequency(); + static const auto freq = otr_get_clock_frequency(); LARGE_INTEGER d; BOOL ret = QueryPerformanceCounter(&d); assert(ret && "QueryPerformanceCounter failed"); - constexpr int usec = 1000000; - unsigned long long tm = d.QuadPart; - tm *= usec; - tm /= freq; - tm %= usec; - tm *= 1000; - - state->tv_sec = (time_t)((unsigned long long)d.QuadPart/freq); - state->tv_nsec = (long)tm; + 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__ diff --git a/compat/timer.hpp b/compat/timer.hpp index f7791a1a..47072226 100644 --- a/compat/timer.hpp +++ b/compat/timer.hpp @@ -23,6 +23,6 @@ struct OTR_COMPAT_EXPORT Timer final private: struct timespec state {}; static void gettime(struct timespec* state); - struct timespec gettime_() const; + struct timespec get_delta() const; using ns = time_units::ns; }; diff --git a/contrib-noinst/important-stuff/game-data.exe b/contrib-noinst/important-stuff/game-data.exe Binary files differnew file mode 100644 index 00000000..4e43a540 --- /dev/null +++ b/contrib-noinst/important-stuff/game-data.exe 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 Binary files differnew file mode 100644 index 00000000..17d13b16 --- /dev/null +++ b/contrib/libusbK-dev-kit/libusbK-3.1.0.0-setup.exe diff --git a/contrib/libusbK-inf-wizard/libusbK-inf-wizard.exe b/contrib/libusbK-inf-wizard/libusbK-inf-wizard.exe Binary files differnew file mode 100644 index 00000000..47c9f175 --- /dev/null +++ b/contrib/libusbK-inf-wizard/libusbK-inf-wizard.exe 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/csv/csv.cpp b/csv/csv.cpp index a1f62dc0..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("(?:\"([^\"]*)\";?)|(?:([^;]*);?)?")); diff --git a/ext-falcon-bms-linear-acc/lang/ru_RU.ts b/csv/lang/de_DE.ts index f62cf2e1..1552582e 100644 --- a/ext-falcon-bms-linear-acc/lang/ru_RU.ts +++ b/csv/lang/de_DE.ts @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1" language="ru_RU"> +<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 d5f33d44..3a8798cc 100644 --- a/cv/CMakeLists.txt +++ b/cv/CMakeLists.txt @@ -4,7 +4,7 @@ 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=17" "-DCXX_STANDARD_REQUIRED=1" + "-DCXX_STANDARD=20" "-DCXX_STANDARD_REQUIRED=1" OUTPUT_VARIABLE krap) otr_module(cv STATIC) target_link_libraries(${self} opencv_core opentrack-video) diff --git a/cv/affine.hpp b/cv/affine.hpp index 882a1145..4640e24e 100644 --- a/cv/affine.hpp +++ b/cv/affine.hpp @@ -7,7 +7,6 @@ #pragma once -#include <opencv2/core.hpp> #include "numeric.hpp" namespace affine_impl { diff --git a/cv/init.cpp b/cv/init.cpp index c9c4650f..d883365b 100644 --- a/cv/init.cpp +++ b/cv/init.cpp @@ -1,6 +1,7 @@ #include "init.hpp" #include <type_traits> -#include <opencv2/core.hpp> +#include <opencv2/core/base.hpp> +#include <opencv2/core/utility.hpp> [[noreturn]] static diff --git a/ext-falcon-bms-linear-acc/lang/stub.ts b/cv/lang/de_DE.ts index 6401616d..1552582e 100644 --- a/ext-falcon-bms-linear-acc/lang/stub.ts +++ b/cv/lang/de_DE.ts @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<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 ce8f7e82..2050e8e4 100644 --- a/cv/numeric.hpp +++ b/cv/numeric.hpp @@ -1,7 +1,7 @@ #pragma once #include <type_traits> -#include <opencv2/core.hpp> +#include <opencv2/core/matx.hpp> namespace numeric_types { using f = float; diff --git a/cv/translation-calibrator.cpp b/cv/translation-calibrator.cpp index dd520ce6..6fb3e638 100644 --- a/cv/translation-calibrator.cpp +++ b/cv/translation-calibrator.cpp @@ -8,6 +8,8 @@ #include "translation-calibrator.hpp" #include "compat/euler.hpp" #include "compat/math.hpp" +#include "compat/macros.h" +#include <opencv2/core.hpp> #include <tuple> diff --git a/cv/translation-calibrator.hpp b/cv/translation-calibrator.hpp index 406edb72..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> //----------------------------------------------------------------------------- diff --git a/cv/video-widget.cpp b/cv/video-widget.cpp index 289b9cee..7accd275 100644 --- a/cv/video-widget.cpp +++ b/cv/video-widget.cpp @@ -1,5 +1,5 @@ #include "video-widget.hpp" - +#include "compat/macros.h" #include <opencv2/imgproc.hpp> void cv_video_widget::update_image(const cv::Mat& frame) @@ -13,15 +13,8 @@ void cv_video_widget::update_image(const cv::Mat& frame) return; cv::Mat const* __restrict scaled = nullptr; - - if (frame3.cols != W || frame3.rows != H) - { - frame3 = cv::Mat(H, W, frame.type()); - frame2 = cv::Mat(H, W, CV_8UC4); - - if (!frame2.isContinuous() || !frame3.isContinuous()) - std::abort(); - } + frame3.create(H, W, frame.type()); + frame2.create(H, W, CV_8UC4); if (frame.cols != W || frame.rows != H) { @@ -37,7 +30,6 @@ void cv_video_widget::update_image(const cv::Mat& frame) scaled = &frame; int color_cvt = cv::COLOR_COLORCVT_MAX; - constexpr int nchannels = 4; switch (scaled->channels()) { @@ -47,7 +39,7 @@ void cv_video_widget::update_image(const cv::Mat& frame) case 3: color_cvt = cv::COLOR_BGR2BGRA; break; - case nchannels: + case 4: break; default: unreachable(); diff --git a/cv/video-widget.hpp b/cv/video-widget.hpp index 54316d32..24f8d7f3 100644 --- a/cv/video-widget.hpp +++ b/cv/video-widget.hpp @@ -8,7 +8,7 @@ #pragma once #include "video/video-widget.hpp" -#include <opencv2/core.hpp> +#include <opencv2/core/mat.hpp> struct cv_video_widget final : video_widget { diff --git a/dinput/dinput.cpp b/dinput/dinput.cpp index 31de40e7..b9713b8a 100644 --- a/dinput/dinput.cpp +++ b/dinput/dinput.cpp @@ -1,10 +1,11 @@ #undef NDEBUG #include "dinput.hpp" -#include "compat/macros.hpp" +#include "compat/macros.h" #include <cassert> #include <cstdlib> +#include <dinput.h> #include <QDebug> @@ -60,7 +61,7 @@ diptr di_t::init_di_() di_t::di_t() = default; -bool di_t::poll_device(LPDIRECTINPUTDEVICE8 dev) +bool di_t::poll_device(IDirectInputDevice8A* dev) { HRESULT hr; assert(handle); diff --git a/dinput/dinput.hpp b/dinput/dinput.hpp index e9908a94..09c9a30b 100644 --- a/dinput/dinput.hpp +++ b/dinput/dinput.hpp @@ -15,7 +15,19 @@ #undef DIRECTINPUT_VERSION #define DIRECTINPUT_VERSION 0x800 -#include <dinput.h> +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 @@ -39,5 +51,5 @@ public: operator bool() const; operator diptr() const; - static bool poll_device(LPDIRECTINPUTDEVICE8 dev); + static bool poll_device(IDirectInputDevice8A* dev); }; diff --git a/dinput/keybinding-worker.cpp b/dinput/keybinding-worker.cpp index 0ceca789..7cd8d663 100644 --- a/dinput/keybinding-worker.cpp +++ b/dinput/keybinding-worker.cpp @@ -9,13 +9,20 @@ #ifdef _WIN32 #include "keybinding-worker.hpp" -#include "compat/macros.hpp" +#include "compat/macros.h" #include "compat/thread-name.hpp" #include <QDebug> #include <QMutexLocker> -#include <windows.h> +#include <dinput.h> + +static void destroy(IDirectInputDevice8A*& dev) +{ + if (dev) + dev->Release(); + dev = nullptr; +} Key::Key() = default; @@ -35,16 +42,13 @@ 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 (dinkeyboard) + if (dev) return true; if (!din) @@ -53,24 +57,39 @@ bool KeybindingWorker::init() goto fail; } - if (din->CreateDevice(GUID_SysKeyboard, &dinkeyboard, nullptr) != DI_OK) + if (auto hr = din->CreateDevice(guid, &dev, nullptr); hr != DI_OK) { - qDebug() << "dinput: create keyboard failed" << GetLastError(); + qDebug() << "dinput: create" << name << "failed" << (void*)hr; goto fail; } - if (dinkeyboard->SetDataFormat(&c_dfDIKeyboard) != DI_OK) + if (auto hr = dev->SetDataFormat(&fmt); hr != DI_OK) { - qDebug() << "dinput: keyboard SetDataFormat" << GetLastError(); + qDebug() << "dinput:" << name << "SetDataFormat" << (void*)hr; goto fail; } - if (dinkeyboard->SetCooperativeLevel((HWND) fake_main_window.winId(), DISCL_NONEXCLUSIVE | DISCL_BACKGROUND) != DI_OK) + if (auto hr = dev->SetCooperativeLevel((HWND) fake_main_window.winId(), DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + hr != DI_OK) { - qDebug() << "dinput: keyboard SetCooperativeLevel" << GetLastError(); + 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 = num_keyboard_states; @@ -79,9 +98,9 @@ bool KeybindingWorker::init() 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() << "dinput: DIPROP_BUFFERSIZE"; + qDebug() << "dinput: keyboard DIPROP_BUFFERSIZE" << (void*)hr; goto fail; } } @@ -89,11 +108,8 @@ bool KeybindingWorker::init() return true; fail: - if (dinkeyboard) - { - dinkeyboard->Release(); - dinkeyboard = nullptr; - } + destroy(dinkeyboard); + destroy(dinmouse); return false; } @@ -125,6 +141,7 @@ void KeybindingWorker::run() bool ok = true; ok &= run_keyboard_nolock(); + ok &= run_mouse_nolock(); ok &= run_joystick_nolock(); if (!ok) @@ -136,6 +153,38 @@ void KeybindingWorker::run() } } +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 @@ -153,6 +202,8 @@ bool KeybindingWorker::run_keyboard_nolock() 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); @@ -164,9 +215,13 @@ bool KeybindingWorker::run_keyboard_nolock() for (unsigned k = 0; k < sz; k++) { - const unsigned idx = keyboard_states[k].dwOfs & 0xff; // defensive programming + 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: @@ -192,8 +247,6 @@ bool KeybindingWorker::run_keyboard_nolock() } break; } - - keystate[idx] = held; } return true; @@ -225,7 +278,7 @@ 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; @@ -241,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; diff --git a/dinput/keybinding-worker.hpp b/dinput/keybinding-worker.hpp index 335a5c11..1c22ca17 100644 --- a/dinput/keybinding-worker.hpp +++ b/dinput/keybinding-worker.hpp @@ -45,7 +45,11 @@ struct OTR_DINPUT_EXPORT KeybindingWorker : private QThread KeybindingWorker& operator=(KeybindingWorker&) = delete; private: - IDirectInputDevice8A* dinkeyboard = nullptr; + 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; @@ -53,21 +57,21 @@ private: di_t din; bool 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); void remove_receiver(fun* pos); ~KeybindingWorker() override; - - static constexpr int num_keyboard_states = 64; - DIDEVICEOBJECTDATA keyboard_states[num_keyboard_states] {}; public: class Token { 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 0f2687fe..cb7a3837 100644 --- a/dinput/win32-joystick.cpp +++ b/dinput/win32-joystick.cpp @@ -1,7 +1,7 @@ #ifdef _WIN32 #include "win32-joystick.hpp" -#include "compat/macros.hpp" +#include "compat/macros.h" #include <cstddef> #include <algorithm> @@ -11,13 +11,13 @@ #include <QWidget> #include <QDebug> +#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; -DIDEVICEOBJECTDATA win32_joy_ctx::joy::keystate_buffers[num_buffers] = {}; void win32_joy_ctx::poll(fn const& f) { @@ -48,7 +48,7 @@ bool win32_joy_ctx::poll_axis(const QString &guid, int* axes) auto& j = iter->second; auto& joy_handle = j->joy_handle; - DIJOYSTATE2 js = {}; + DIJOYSTATE2 js; if (!di_t::poll_device(joy_handle)) continue; @@ -142,6 +142,8 @@ bool win32_joy_ctx::joy::poll(fn const& f) return false; } + DIDEVICEOBJECTDATA keystate_buffers[num_buffers]; + DWORD sz = num_buffers; if (FAILED(hr = joy_handle->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), keystate_buffers, &sz, 0))) { @@ -226,6 +228,9 @@ win32_joy_ctx::enum_state::~enum_state() void win32_joy_ctx::enum_state::refresh() { all.clear(); +#ifdef __SANITIZE_ADDRESS__ + return; +#endif if (!di) { @@ -270,7 +275,7 @@ 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() << "dinput: failed joystick CreateDevice" << guid << (void*)hr; @@ -336,7 +341,7 @@ 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() << "dinput: failed joystick DIPROP_RANGE" << (void*)hr; return DIENUM_STOP; @@ -346,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; diff --git a/dinput/win32-joystick.hpp b/dinput/win32-joystick.hpp index 82590db6..8e5344d6 100644 --- a/dinput/win32-joystick.hpp +++ b/dinput/win32-joystick.hpp @@ -10,6 +10,7 @@ #include "dinput.hpp" #include "compat/timer.hpp" #include "export.hpp" +#include "compat/qhash.hpp" #include <memory> #include <vector> @@ -22,9 +23,8 @@ namespace win32_joy_impl { -static constexpr unsigned max_buttons = std::size(DIJOYSTATE2().rgbButtons); -static constexpr unsigned max_pov_hats = std::size(DIJOYSTATE2().rgdwPOV); - +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) @@ -47,13 +47,11 @@ struct OTR_DINPUT_EXPORT win32_joy_ctx final struct joy final { - LPDIRECTINPUTDEVICE8 joy_handle; + IDirectInputDevice8A* joy_handle; QString guid, name; bool last_state[max_buttons_and_pov_hats] {}; - static DIDEVICEOBJECTDATA keystate_buffers[num_buffers]; - - joy(LPDIRECTINPUTDEVICE8 handle, const QString& guid, const QString& name); + joy(IDirectInputDevice8A* handle, const QString& guid, const QString& name); ~joy(); void release(); @@ -88,8 +86,8 @@ private: joys_t joys; 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; diff --git a/ext-falcon-bms-linear-acc/CMakeLists.txt b/ext-falcon-bms-linear-acc/CMakeLists.txt deleted file mode 100644 index bb284f06..00000000 --- a/ext-falcon-bms-linear-acc/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -if(WIN32) - #otr_module(ext-falcon-bms-acceleration) -endif() diff --git a/ext-falcon-bms-linear-acc/FlightData.h b/ext-falcon-bms-linear-acc/FlightData.h deleted file mode 100644 index 3f7e99ab..00000000 --- a/ext-falcon-bms-linear-acc/FlightData.h +++ /dev/null @@ -1,578 +0,0 @@ -#pragma once - -#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" *** -struct FlightData -{ - // 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 : unsigned - { - 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 : unsigned - { - // 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 : unsigned - { - // 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 : unsigned - { - 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; - -#if 0 - void SetLightBit (unsigned int newBit) {lightBits |= newBit;}; - void ClearLightBit(unsigned int newBit) { lightBits &= ~newBit; }; - bool IsSet(unsigned int newBit) { return ((lightBits & newBit) != 0); }; - - void SetLightBit2(unsigned int newBit) { lightBits2 |= newBit; }; - void ClearLightBit2(unsigned int newBit) { lightBits2 &= ~newBit; }; - bool IsSet2(unsigned int newBit) { return ((lightBits2 & newBit) != 0); }; - - void SetLightBit3(unsigned int newBit) { lightBits3 |= newBit; }; - void ClearLightBit3(unsigned int newBit) { lightBits3 &= ~newBit; }; - bool IsSet3(unsigned int newBit) { return ((lightBits3 & newBit) != 0); }; - - void SetHsiBit(unsigned int newBit) { hsiBits |= newBit; }; - void ClearHsiBit(unsigned int newBit) { hsiBits &= ~newBit; }; - bool IsSetHsi(unsigned int newBit) { return ((hsiBits & newBit) != 0); }; -#endif - - 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 - -struct OsbLabel { - char line1[OSB_STRING_LENGTH]; - char line2[OSB_STRING_LENGTH]; - bool inverted; -}; - - -// *** "FalconSharedOsbMemoryArea" *** -struct OSBData -{ - 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" *** -struct FlightData2 -{ - // TACAN - enum TacanSources : unsigned - { - UFC = 0, - AUX = 1, - NUMBER_OF_SOURCES = 2, - }; - enum TacanBits : unsigned - { - 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 : unsigned - { - 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 : unsigned - { - 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 : unsigned - { - // 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 : unsigned - { - CmdsOFF = 0, - CmdsSTBY = 1, - CmdsMAN = 2, - CmdsSEMI = 3, - CmdsAUTO = 4, - CmdsBYP = 5, - }; - - // HSI/eHSI mode state - enum NavModes : unsigned - { - ILS_TACAN = 0, - TACAN = 1, - NAV = 2, - ILS_NAV = 3, - }; - - // human pilot state - enum FlyStates : unsigned - { - 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 - -#if 0 - // 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) != 0); } - bool AuxTacanIsAA(void) {return ((tacanInfo[AUX] & mode) != 0); } - bool UfcTacanIsX(void) {return ((tacanInfo[UFC] & band) != 0); } - bool AuxTacanIsX(void) {return ((tacanInfo[AUX] & band) != 0); } - - // ALTIMETER - void SetAltBit(unsigned int newBit) { altBits |= newBit; }; - void ClearAltBit(unsigned int newBit) { altBits &= ~newBit; }; - bool IsSetAlt(unsigned int newBit) { return ((altBits & newBit) != 0); }; - - // POWER - void SetPowerBit(unsigned int newBit) { powerBits |= newBit; }; - void ClearPowerBit(unsigned int newBit) { powerBits &= ~newBit; }; - bool IsSetPower(unsigned int newBit) { return ((powerBits & newBit) != 0); }; - - // BLINKING LIGHTS - void SetBlinkBit(unsigned int newBit) { blinkBits |= newBit; }; - void ClearBlinkBit(unsigned int newBit) { blinkBits &= ~newBit; }; - bool IsSetBlink(unsigned int newBit) { return ((blinkBits & newBit) != 0); }; - - // 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;}; -#endif -}; - -#define BMS_MEM_OSB "FalconSharedOsbMemoryArea" -#define BMS_MEM_DATA1 "FalconSharedMemoryArea" -#define BMS_MEM_DATA2 "FalconSharedMemoryArea2" - -#if 0 -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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;"> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Accela + Hamilton Filter</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">For both rotations and positions: No movement occurs within the dead zone.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">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.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">When you lean forward, smoothing is added up to the set maximum at the distance of Max Z.</p></body></html></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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;"> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Accela + Hamilton Filter</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">For both rotations and positions: No movement occurs within the dead zone.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">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.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">When you lean forward, smoothing is added up to the set maximum at the distance of Max Z.</p></body></html></source> + <translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;"> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Accela + Hamilton Filter</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Für Rotation und Position: Es erfolgt keine Bewegung innerhalb des Totbereichs.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">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.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Beim Vorbeugen wird eine Glättung bis zum eingestellten Maximum der Distanz Max Z hinzugefügt.</p></body></html></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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;"> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Accela + Hamilton Filter</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">For both rotations and positions: No movement occurs within the dead zone.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">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.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">When you lean forward, smoothing is added up to the set maximum at the distance of Max Z.</p></body></html></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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;"> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Accela + Hamilton Filter</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">For both rotations and positions: No movement occurs within the dead zone.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">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.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">When you lean forward, smoothing is added up to the set maximum at the distance of Max Z.</p></body></html></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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;"> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Accela + Hamilton Filter</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">For both rotations and positions: No movement occurs within the dead zone.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">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.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">When you lean forward, smoothing is added up to the set maximum at the distance of Max Z.</p></body></html></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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;"> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Accela + Hamilton Filter</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">For both rotations and positions: No movement occurs within the dead zone.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">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.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">When you lean forward, smoothing is added up to the set maximum at the distance of Max Z.</p></body></html></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/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 44a341e0..940ac50c 100644 --- a/filter-accela/ftnoir_filter_accela.cpp +++ b/filter-accela/ftnoir_filter_accela.cpp @@ -7,6 +7,7 @@ #include "ftnoir_filter_accela.h" #include "compat/math.hpp" #include "api/plugin-api.hpp" +#include "opentrack/defs.hpp" #include <algorithm> #include <QDebug> @@ -37,7 +38,7 @@ static void do_deltas(const double* deltas, double* output, F&& fun) 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; } @@ -62,8 +63,19 @@ static void do_deltas(const double* deltas, double* output, F&& fun) } } +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; @@ -98,6 +110,7 @@ void accela::filter(const double* input, double *output) 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); @@ -107,9 +120,16 @@ void accela::filter(const double* input, double *output) deltas[i] = d / rot_thres; } +#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); }); +#endif // pos @@ -134,6 +154,8 @@ 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]; } diff --git a/filter-accela/ftnoir_filter_accela.h b/filter-accela/ftnoir_filter_accela.h index 6471a7b8..0d6dca1b 100644 --- a/filter-accela/ftnoir_filter_accela.h +++ b/filter-accela/ftnoir_filter_accela.h @@ -12,7 +12,6 @@ #include "api/plugin-api.hpp" #include "compat/timer.hpp" #include "compat/variance.hpp" -#include "compat/macros.hpp" #include <QMutex> #include <QTimer> @@ -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(); diff --git a/filter-accela/ftnoir_filter_accela_dialog.cpp b/filter-accela/ftnoir_filter_accela_dialog.cpp index 37f265e1..6a0a35b0 100644 --- a/filter-accela/ftnoir_filter_accela_dialog.cpp +++ b/filter-accela/ftnoir_filter_accela_dialog.cpp @@ -78,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><html><head/><body><p align="right"><br/><span style=" font-size:10pt;">Accela by </span><a href="https://github.com/sthalik"><span style=" font-size:10pt; text-decoration: underline; color:#0057ae;">Stanisław Halik</span></a><span style=" font-size:10pt;"><br/>Thanks to </span><a href="https://github.com/dbaarda"><span style=" font-size:10pt; text-decoration: underline; color:#0057ae;">Donovan Baarda</span></a></p><p align="right"><span style=" font-size:10pt;">2012-2017</span></p><p align="right"><br/><span style=" font-size:8pt;">Visit </span><a href="https://github.com/opentrack/opentrack/wiki/Accela-in-opentrack-2.3"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">our wiki</span></a><span style=" font-size:8pt;"> for description of the settings.</span></p></body></html></source> + <translation><html><head/><body><p align="right"><br/><span style=" font-size:10pt;">Accela by </span><a href="https://github.com/sthalik"><span style=" font-size:10pt; text-decoration: underline; color:#0057ae;">Stanisław Halik</span></a><span style=" font-size:10pt;"><br/>Thanks to </span><a href="https://github.com/dbaarda"><span style=" font-size:10pt; text-decoration: underline; color:#0057ae;">Donovan Baarda</span></a></p><p align="right"><span style=" font-size:10pt;">2012-2017</span></p><p align="right"><br/><span style=" font-size:8pt;">In </span><a href="https://github.com/opentrack/opentrack/wiki/Accela-in-opentrack-2.3"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">unserem Wiki</span></a><span style=" font-size:8pt;"> gibt es eine Beschreibung der Einstellungen.</span></p></body></html></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/zh_CN.ts b/filter-accela/lang/zh_CN.ts index 4b77b3d7..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,14 +33,39 @@ </message> <message> <source><html><head/><body><p align="right"><br/><span style=" font-size:10pt;">Accela by </span><a href="https://github.com/sthalik"><span style=" font-size:10pt; text-decoration: underline; color:#0057ae;">Stanisław Halik</span></a><span style=" font-size:10pt;"><br/>Thanks to </span><a href="https://github.com/dbaarda"><span style=" font-size:10pt; text-decoration: underline; color:#0057ae;">Donovan Baarda</span></a></p><p align="right"><span style=" font-size:10pt;">2012-2017</span></p><p align="right"><br/><span style=" font-size:8pt;">Visit </span><a href="https://github.com/opentrack/opentrack/wiki/Accela-in-opentrack-2.3"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">our wiki</span></a><span style=" font-size:8pt;"> for description of the settings.</span></p></body></html></source> - <translation type="unfinished"></translation> + <translation type="unfinished"><html> + +<body> + <p align="right"><br /> + <span style=" font-size:10pt;">Accela by </span> + <a href="https://github.com/sthalik"> + <span style=" font-size:10pt; text-decoration: underline; color:#0057ae;">Stanisław Halik</span> + </a> + <span style=" font-size:10pt;"><br />Thanks to </span> + <a href="https://github.com/dbaarda"> + <span style=" font-size:10pt; text-decoration: underline; color:#0057ae;">Donovan Baarda</span> + </a> + </p> + <p align="right"> + <span style=" font-size:10pt;">2012-2017</span> + </p> + <p align="right"><br /> + <span style=" font-size:8pt;">访问</span> + <a href="https://github.com/opentrack/opentrack/wiki/Accela-in-opentrack-2.3"> + <span style=" font-size:8pt; text-decoration: underline; color:#0066bb;">我们的 wiki</span> + </a> + <span style=" font-size:8pt;"> 了解设置的描述.</span> + </p> +</body> + +</html></translation> </message> </context> <context> <name>accelaDll</name> <message> <source>Accela</source> - <translation type="unfinished"></translation> + <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> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Give the filter up to 60 seconds to warm up and stop shaking.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Give the filter a few seconds to warm up and stop shaking.</span></p> <p style="-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;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Min:</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Defines the way the filter responds to fast movements;</span></p> diff --git a/filter-ewma2/ftnoir_filter_ewma2.cpp b/filter-ewma2/ftnoir_filter_ewma2.cpp index ce6cd040..686552ba 100644 --- a/filter-ewma2/ftnoir_filter_ewma2.cpp +++ b/filter-ewma2/ftnoir_filter_ewma2.cpp @@ -26,22 +26,24 @@ 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); @@ -57,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]; @@ -70,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 1cd30a6b..5698157d 100644 --- a/filter-ewma2/ftnoir_filter_ewma2.h +++ b/filter-ewma2/ftnoir_filter_ewma2.h @@ -28,9 +28,10 @@ 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]; 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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Give the filter a few seconds to warm up and stop shaking.</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Min:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Defines the way the filter responds to fast movements;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Higher value: slower response;</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Max:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Defines the way the filter responds to slow movements;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Higher value: slower response;</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Pow:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Defines the filters 'readiness' to respond to speed changes;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Higher value = </span><span style=" font-size:10pt; font-weight:600;">faster</span><span style=" font-size:10pt;"> response;</span></p> +<p style="-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;"><br /></p></body></html></source> + <translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Bitte einige Sekunden warten, bis der Filter warm gelaufen ist und nicht mehr zittert.</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Minimum:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Definiert, wie der Filter auf schnelle Bewegungen reagiert;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Höherer Wert: langsameres Ansprechen;</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Maximum:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Definiert, wie der Filter auf langsame Bewegungen reagiert;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Höherer Wert: langsameres Ansprechen;</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Stärke:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Definiert die 'Bereitschaft' des Filters, auf Geschwindigkeitsänderungen zu reagieren;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Höherer Wert = </span><span style=" font-size:10pt; font-weight:600;">schnelleres</span><span style=" font-size:10pt;"> Ansprechen;</span></p> +<p style="-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;"><br /></p></body></html></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 b4153053..8c718b7d 100644 --- a/filter-ewma2/lang/nl_NL.ts +++ b/filter-ewma2/lang/nl_NL.ts @@ -28,7 +28,7 @@ <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Give the filter up to 60 seconds to warm up and stop shaking.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Give the filter a few seconds to warm up and stop shaking.</span></p> <p style="-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;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Min:</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Defines the way the filter responds to fast movements;</span></p> diff --git a/filter-ewma2/lang/ru_RU.ts b/filter-ewma2/lang/ru_RU.ts index f8517c87..14afb40e 100644 --- a/filter-ewma2/lang/ru_RU.ts +++ b/filter-ewma2/lang/ru_RU.ts @@ -28,7 +28,7 @@ <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Give the filter up to 60 seconds to warm up and stop shaking.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Give the filter a few seconds to warm up and stop shaking.</span></p> <p style="-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;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Min:</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Defines the way the filter responds to fast movements;</span></p> diff --git a/filter-ewma2/lang/stub.ts b/filter-ewma2/lang/stub.ts index dc39f91f..fcce862e 100644 --- a/filter-ewma2/lang/stub.ts +++ b/filter-ewma2/lang/stub.ts @@ -28,7 +28,7 @@ <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Give the filter up to 60 seconds to warm up and stop shaking.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Give the filter a few seconds to warm up and stop shaking.</span></p> <p style="-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;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Min:</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Defines the way the filter responds to fast movements;</span></p> diff --git a/filter-ewma2/lang/zh_CN.ts b/filter-ewma2/lang/zh_CN.ts index dc39f91f..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 @@ <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Give the filter up to 60 seconds to warm up and stop shaking.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Give the filter a few seconds to warm up and stop shaking.</span></p> <p style="-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;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Min:</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Defines the way the filter responds to fast movements;</span></p> @@ -42,7 +42,24 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Defines the filters 'readiness' to respond to speed changes;</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Higher value = </span><span style=" font-size:10pt; font-weight:600;">faster</span><span style=" font-size:10pt;"> response;</span></p> <p style="-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;"><br /></p></body></html></source> - <translation type="unfinished"></translation> + <translation type="unfinished"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">给过滤器一点时间来完成预热与消除抖动. </span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Min:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">定义滤波器对快速运动的响应方式; </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">数值越大, 响应越慢; </span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Max:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">定义滤波器对慢速运动的响应方式; </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">数值越大, 响应越慢; </span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Pow:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">定义滤波器'readiness' to respond to speed changes;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">更大数值 = </span><span style=" font-size:10pt; font-weight:600;">更快</span><span style=" font-size:10pt;"> 响应;</span></p> +<p style="-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;"><br /></p></body></html></translation> </message> </context> <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缩小至最小值.<a href="https://github.com/opentrack/opentrack/pull/1667">阅读此处Pull#1167</a></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">当您向前倾时, "缩放时平滑" 功能会增强旋转的平滑强度. 前倾到 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 1960e0f6..00000000 --- a/filter-kalman/kalman.cpp +++ /dev/null @@ -1,323 +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 <cmath> -#include <QDebug> - -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; -} - -void DeadzoneFilter::reset() -{ - last_output = PoseVector::Zero(); -} - -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 = settings::process_sigma_pos; - double sigma_angle = settings::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; - } - dt_since_last_input = 0; - - prev_slider_pos[0] = s.noise_pos_slider_value; - prev_slider_pos[1] = 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() * settings::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&) -{ - this->ui.noiseRotLabel->setText( - QString::number(settings::map_slider_value(s.noise_rot_slider_value), 'f', 3) + "°"); - - 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(); -} - -double settings::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 = int(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 -} - -OPENTRACK_DECLARE_FILTER(kalman, dialog_kalman, kalmanDll) diff --git a/filter-kalman/kalman.h b/filter-kalman/kalman.h deleted file mode 100644 index 1e6e2ac4..00000000 --- a/filter-kalman/kalman.h +++ /dev/null @@ -1,159 +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 "compat/timer.hpp" -#include "api/plugin-api.hpp" -#include "options/options.hpp" -using namespace options; - -// Eigen can't check for SSE3 on MSVC -#if defined _MSC_VER && defined __SSE2__ -# define EIGEN_VECTORIZE_SSE3 -// this hardware is 10 years old -# define EIGEN_VECTORIZE_SSE4_1 -# define EIGEN_VECTORIZE_SSE4_2 -#endif - -// nodiscard for placement new -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable : 4834) -#endif - -#include <Eigen/Core> -#include <Eigen/LU> - -#ifdef _MSC_VER -# pragma warning(pop) -#endif - -#include "ui_ftnoir_kalman_filtercontrols.h" -#include <QString> -#include <QWidget> - -static constexpr int NUM_STATE_DOF = 12; -static constexpr 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); -}; - -struct KalmanProcessNoiseScaler -{ - MeasureMatrix - innovation_cov_estimate; - StateMatrix - base_cov; // baseline (unscaled) process noise covariance matrix - void init(); - void update(KalmanFilter &kf, double dt); -}; - - -struct DeadzoneFilter -{ - PoseVector last_output { PoseVector::Zero() }, - dz_size { PoseVector::Zero() }; - - DeadzoneFilter() = default; - void reset(); - PoseVector filter(const PoseVector &input); -}; - - -struct settings : opts { - value<slider_value> noise_rot_slider_value { b, "noise-rotation-slider", { .5, 0, 1 } }; - value<slider_value> noise_pos_slider_value { b, "noise-position-slider", { .5, 0, 1 } }; - - static constexpr double adaptivity_window_length = 0.25; // seconds - static constexpr double deadzone_scale = 8; - static constexpr double deadzone_exponent = 2.0; - static constexpr double process_sigma_pos = 0.5; - static constexpr double process_sigma_rot = 0.5; - - static double map_slider_value(const slider_value &v); - - settings() : opts("kalman-filter") {} -}; - -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() override { return status_ok(); } - - double dt_since_last_input; - PoseVector last_input; - KalmanFilter kf; - KalmanProcessNoiseScaler kf_adaptive_process_noise_cov; - DeadzoneFilter dz_filter; - settings s; - slider_value prev_slider_pos[2] { - *s.noise_pos_slider_value, - *s.noise_rot_slider_value, - }; - Timer timer; - - bool first_run = true; -}; - -class kalmanDll : public Metadata -{ - Q_OBJECT - - QString name() override { return tr("Kalman"); } - QIcon icon() override { 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-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/trackmouse/lang/nl_NL.ts b/filter-nm/lang/nl_NL.ts index 92f790af..540e2d51 100644 --- a/trackmouse/lang/nl_NL.ts +++ b/filter-nm/lang/nl_NL.ts @@ -2,72 +2,60 @@ <!DOCTYPE TS> <TS version="2.1" language="nl_NL"> <context> - <name>main_window</name> + <name>UICdialog_nm</name> <message> - <source>The Octopus is sad</source> + <source>Dialog</source> <translation type="unfinished"></translation> </message> <message> - <source> :: </source> + <source>°/s</source> <translation type="unfinished"></translation> </message> <message> - <source>Check permissions for your .ini directory: - -%1"%2 - -Exiting now.</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>window</name> - <message> - <source>trackmouse prototype</source> + <source>mm/s</source> <translation type="unfinished"></translation> </message> <message> - <source>Keyboard shortcuts</source> + <source>Responsiveness</source> <translation type="unfinished"></translation> </message> <message> - <source>start/stop tracking</source> + <source>10.0</source> <translation type="unfinished"></translation> </message> <message> - <source>Alt+F10</source> + <source>Drift speeds</source> <translation type="unfinished"></translation> </message> <message> - <source>center</source> + <source>50</source> <translation type="unfinished"></translation> </message> <message> - <source>Alt+F11</source> + <source>100</source> <translation type="unfinished"></translation> </message> <message> - <source>Alt+F12</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>Sensitivity</source> + <source>Rotation</source> <translation type="unfinished"></translation> </message> <message> - <source>100%</source> + <source>Position</source> <translation type="unfinished"></translation> </message> <message> - <source>Start</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>Stop</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>freeze toggle</source> + <source>NaturalMovement</source> <translation type="unfinished"></translation> </message> </context> diff --git a/trackmouse/lang/ru_RU.ts b/filter-nm/lang/ru_RU.ts index dd9011c4..31e5d5cc 100644 --- a/trackmouse/lang/ru_RU.ts +++ b/filter-nm/lang/ru_RU.ts @@ -2,72 +2,60 @@ <!DOCTYPE TS> <TS version="2.1" language="ru_RU"> <context> - <name>main_window</name> + <name>UICdialog_nm</name> <message> - <source>The Octopus is sad</source> + <source>Dialog</source> <translation type="unfinished"></translation> </message> <message> - <source> :: </source> + <source>°/s</source> <translation type="unfinished"></translation> </message> <message> - <source>Check permissions for your .ini directory: - -%1"%2 - -Exiting now.</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>window</name> - <message> - <source>trackmouse prototype</source> + <source>mm/s</source> <translation type="unfinished"></translation> </message> <message> - <source>Keyboard shortcuts</source> + <source>Responsiveness</source> <translation type="unfinished"></translation> </message> <message> - <source>start/stop tracking</source> + <source>10.0</source> <translation type="unfinished"></translation> </message> <message> - <source>Alt+F10</source> + <source>Drift speeds</source> <translation type="unfinished"></translation> </message> <message> - <source>center</source> + <source>50</source> <translation type="unfinished"></translation> </message> <message> - <source>Alt+F11</source> + <source>100</source> <translation type="unfinished"></translation> </message> <message> - <source>Alt+F12</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>Sensitivity</source> + <source>Rotation</source> <translation type="unfinished"></translation> </message> <message> - <source>100%</source> + <source>Position</source> <translation type="unfinished"></translation> </message> <message> - <source>Start</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>Stop</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>freeze toggle</source> + <source>NaturalMovement</source> <translation type="unfinished"></translation> </message> </context> diff --git a/trackmouse/lang/zh_CN.ts b/filter-nm/lang/stub.ts index 968e31c5..3b960804 100644 --- a/trackmouse/lang/zh_CN.ts +++ b/filter-nm/lang/stub.ts @@ -1,73 +1,61 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<TS version="2.1" language="stub"> <context> - <name>main_window</name> + <name>UICdialog_nm</name> <message> - <source>The Octopus is sad</source> + <source>Dialog</source> <translation type="unfinished"></translation> </message> <message> - <source> :: </source> + <source>°/s</source> <translation type="unfinished"></translation> </message> <message> - <source>Check permissions for your .ini directory: - -%1"%2 - -Exiting now.</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>window</name> - <message> - <source>trackmouse prototype</source> + <source>mm/s</source> <translation type="unfinished"></translation> </message> <message> - <source>Keyboard shortcuts</source> + <source>Responsiveness</source> <translation type="unfinished"></translation> </message> <message> - <source>start/stop tracking</source> + <source>10.0</source> <translation type="unfinished"></translation> </message> <message> - <source>Alt+F10</source> + <source>Drift speeds</source> <translation type="unfinished"></translation> </message> <message> - <source>center</source> + <source>50</source> <translation type="unfinished"></translation> </message> <message> - <source>Alt+F11</source> + <source>100</source> <translation type="unfinished"></translation> </message> <message> - <source>Alt+F12</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>Sensitivity</source> + <source>Rotation</source> <translation type="unfinished"></translation> </message> <message> - <source>100%</source> + <source>Position</source> <translation type="unfinished"></translation> </message> <message> - <source>Start</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>Stop</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>freeze toggle</source> + <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/gui/CMakeLists.txt b/gui/CMakeLists.txt index db2f0b9e..8c57221a 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -11,7 +11,12 @@ target_link_libraries(${self} if(APPLE) target_link_libraries(${self} proc) elseif(LINUX) - otr_pkgconfig(${self} libprocps) + 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(NOT APPLE AND NOT WIN32) diff --git a/gui/correlation-calibrator.ui b/gui/correlation-calibrator.ui index d351d5da..02fdf1d2 100644 --- a/gui/correlation-calibrator.ui +++ b/gui/correlation-calibrator.ui @@ -40,7 +40,7 @@ Press "clear calibration" 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 Binary files differindex 187aabf9..87d9f7d7 100644 --- a/gui/images/english.png +++ b/gui/images/english.png diff --git a/gui/images/settings16.png b/gui/images/settings16.png Binary files differindex 3b31623b..fa2e4a11 100644 --- a/gui/images/settings16.png +++ b/gui/images/settings16.png diff --git a/gui/init.cpp b/gui/init.cpp index 3aae5772..b666b4a9 100644 --- a/gui/init.cpp +++ b/gui/init.cpp @@ -21,7 +21,6 @@ using namespace options; #include <QStyleFactory> #include <QLocale> #include <QTranslator> -#include <QApplication> #include <QDir> #include <QFile> #include <QFileDialog> @@ -51,8 +50,10 @@ static void set_fp_mask() #endif #ifdef __APPLE__ +#if defined __i386__ || defined __x86_64__ fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV); #endif +#endif #ifdef _WIN32 # ifdef __clang__ @@ -159,53 +160,91 @@ static void qdebug_to_console(QtMsgType loglevel, const QMessageLogContext& ctx, #ifdef _WIN32 +static void apply_dark_windows_theme_if_needed() +{ + // 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://software.intel.com/en-us/articles/limitation-to-the-length-of-the-system-path-variable - static char env_path[4096] { '\0', }; + // 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 + { + 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 - qDebug() << "can't set win32 path"; + 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(); } } @@ -263,17 +302,18 @@ int otr_main(int argc, char** argv, std::function<std::unique_ptr<QWidget>()> co #endif QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); -#endif 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); @@ -314,4 +354,3 @@ int otr_main(int argc, char** argv, std::function<std::unique_ptr<QWidget>()> co return ret; } - diff --git a/gui/init.hpp b/gui/init.hpp index 4274f437..28ff55c7 100644 --- a/gui/init.hpp +++ b/gui/init.hpp @@ -8,8 +8,6 @@ OTR_GUI_EXPORT int otr_main(int argc, char** argv, std::function<std::unique_ptr<QWidget>()> const& make_main_window); -// XXX TODO need split MainWindow into mixins each implementing part of the functionality - template<typename F> auto run_application(int argc, char** argv, F&& fun) { 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 "calibrate" in given row to calibrate that axis. Follow instructions in the next window. +Press "clear calibration" 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 60b84189..1b9d8fe9 100644 --- a/gui/lang/nl_NL.ts +++ b/gui/lang/nl_NL.ts @@ -141,10 +141,6 @@ Press "clear calibration" to remove any calibration data pertaining to <translation>Algehele sneltoetsen</translation> </message> <message> - <source><html><head/><body><p><span style=" font-weight:600;">Center</span> - use current pose as looking perfectly forward.<br/><span style=" font-weight:600;">Toggle</span> - keep looking at same spot until next toggle keypress.<br/><span style=" font-weight:600;">Zero</span> - keep looking forward until next zero keypress.<br/></p></body></html></source> - <translation><html><head/><body><p><span style=" font-weight:600;">Centreren</span> - gebruik de huidige kijkrichting als ijkpunt voor het centreren.<br/><span style=" font-weight:600;">Wisselen</span> - kijkrichting blijft op dezelfde plek staan, totdat de ingestelde knop opnieuw wordt ingedrukt.<br/><span style=" font-weight:600;">Zero</span> - Kijkrichting blijft recht vooruit, totdat er op de ingestelde toets gedrukt wordt.<br/></p></body></html></translation> - </message> - <message> <source>Bind</source> <translation>Koppelen</translation> </message> @@ -285,27 +281,15 @@ Press "clear calibration" to remove any calibration data pertaining to <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> @@ -360,6 +344,58 @@ Press "clear calibration" to remove any calibration data pertaining to <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 d40b4775..5b3fcc03 100644 --- a/gui/lang/ru_RU.ts +++ b/gui/lang/ru_RU.ts @@ -141,11 +141,6 @@ Press "clear calibration" to remove any calibration data pertaining to <translation>Глобальные горячие клавиши</translation> </message> <message> - <source><html><head/><body><p><span style=" font-weight:600;">Center</span> - use current pose as looking perfectly forward.<br/><span style=" font-weight:600;">Toggle</span> - keep looking at same spot until next toggle keypress.<br/><span style=" font-weight:600;">Zero</span> - keep looking forward until next zero keypress.<br/></p></body></html></source> - <translatorcomment>использовать текущую позу как позу вперед</translatorcomment> - <translation><html><head/><body><p><span style=" font-weight:600;">Центрирование</span> - использовать текущую позу в качестве центра.<br/><span style=" font-weight:600;">Фиксация обзора</span> - фиксация обзора до следующего нажатия клавиши.<br/><span style=" font-weight:600;">Центр</span> - фиксирует взгляд в нулевой точке до следующего нажатия клавиши.<br/></p></body></html></translation> - </message> - <message> <source>Bind</source> <translation>Назначить</translation> </message> @@ -190,10 +185,6 @@ Press "clear calibration" to remove any calibration data pertaining to <translation>Центрирование при запуске</translation> </message> <message> - <source>Never translate the application interface</source> - <translation></translation> - </message> - <message> <source>Minimize to tray</source> <translation>Настройка трея</translation> </message> @@ -276,22 +267,10 @@ Press "clear calibration" to remove any calibration data pertaining to <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> @@ -363,6 +342,62 @@ Press "clear calibration" to remove any calibration data pertaining to <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 b383a0d8..ee96d3be 100644 --- a/gui/lang/stub.ts +++ b/gui/lang/stub.ts @@ -141,10 +141,6 @@ Press "clear calibration" to remove any calibration data pertaining to <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-weight:600;">Center</span> - use current pose as looking perfectly forward.<br/><span style=" font-weight:600;">Toggle</span> - keep looking at same spot until next toggle keypress.<br/><span style=" font-weight:600;">Zero</span> - keep looking forward until next zero keypress.<br/></p></body></html></source> - <translation type="unfinished"></translation> - </message> - <message> <source>Bind</source> <translation type="unfinished"></translation> </message> @@ -189,10 +185,6 @@ Press "clear calibration" to remove any calibration data pertaining to <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> @@ -293,22 +285,10 @@ Press "clear calibration" to remove any calibration data pertaining to <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> @@ -360,6 +340,62 @@ Press "clear calibration" to remove any calibration data pertaining to <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 fa2d512c..4914f7e1 100644 --- a/gui/lang/zh_CN.ts +++ b/gui/lang/zh_CN.ts @@ -142,10 +142,6 @@ Press "clear calibration" to remove any calibration data pertaining to <translation>全局快捷键</translation> </message> <message> - <source><html><head/><body><p><span style=" font-weight:600;">Center</span> - use current pose as looking perfectly forward.<br/><span style=" font-weight:600;">Toggle</span> - keep looking at same spot until next toggle keypress.<br/><span style=" font-weight:600;">Zero</span> - keep looking forward until next zero keypress.<br/></p></body></html></source> - <translation><html><head/><body><p><span style=" font-weight:600;">回中位置</span> - 以现在的姿势为最完美向前看.<br/><span style=" font-weight:600;">盯着看</span> - 盯着固定位置直到再次按键.<br/><span style=" font-weight:600;">归零位置</span> - 保持向前看直到再次按键.<br/></p></body></html></translation> - </message> - <message> <source>Center</source> <translation>回中位置</translation> </message> @@ -187,10 +183,10 @@ Press "clear calibration" to remove any calibration data pertaining to </message> <message> <source>Center at startup</source> - <translation>启动时自动回中</translation> + <translation>启动时回中</translation> </message> <message> - <source>Never translate the application interface</source> + <source>Disable user interface localization</source> <translation>关闭翻译界面</translation> </message> <message> @@ -210,10 +206,6 @@ Press "clear calibration" to remove any calibration data pertaining to <translation>输出</translation> </message> <message> - <source>Output remap</source> - <translation>输出重新映射</translation> - </message> - <message> <source>X</source> <translation></translation> </message> @@ -250,24 +242,16 @@ Press "clear calibration" to remove any calibration data pertaining to <translation>源头</translation> </message> <message> - <source>Invert</source> - <translation>反向</translation> - </message> - <message> <source>Destination</source> <translation>目标</translation> </message> <message> - <source>Assign input axis to output axis.</source> - <translation>指定输入坐标到输出坐标.</translation> - </message> - <message> <source>Custom center pose</source> <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>改变游戏中的得到的回中位置. 对于默认位置有明显偏移的很有用.</translation> + <translation>改变发送到游戏的回中位置. 对于默认位置有明显偏移的很有用.</translation> </message> <message> <source>°</source> @@ -275,7 +259,7 @@ Press "clear calibration" to remove any calibration data pertaining to </message> <message> <source> cm</source> - <translation> 厘米</translation> + <translation></translation> </message> <message> <source>CSV Data Logging</source> @@ -361,6 +345,58 @@ Press "clear calibration" to remove any calibration data pertaining to <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> <context> <name>process_detector</name> diff --git a/gui/mapping-dialog.cpp b/gui/mapping-dialog.cpp index af35c999..cdbf532e 100644 --- a/gui/mapping-dialog.cpp +++ b/gui/mapping-dialog.cpp @@ -59,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); } @@ -96,9 +100,13 @@ void mapping_dialog::load() 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 }) + 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); + 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++) { const bool altp = qfcs[i].altp; @@ -122,8 +130,12 @@ void mapping_dialog::load() value = 1; 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); }; @@ -139,6 +151,13 @@ void mapping_dialog::load() value = 10; break; 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); }; diff --git a/gui/mapping-dialog.hpp b/gui/mapping-dialog.hpp index 09170c62..3758e7c7 100644 --- a/gui/mapping-dialog.hpp +++ b/gui/mapping-dialog.hpp @@ -16,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 8d72533d..545b5ae5 100644 --- a/gui/mapping-dialog.ui +++ b/gui/mapping-dialog.ui @@ -352,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> @@ -432,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> @@ -512,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 3851f0c2..76c3313a 100644 --- a/gui/settings.cpp +++ b/gui/options-dialog.cpp @@ -6,7 +6,7 @@ * notice appear in all copies. */ -#include "settings.hpp" +#include "options-dialog.hpp" #include "keyboard.h" #include "compat/library-path.hpp" @@ -30,7 +30,10 @@ 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 (kopts.keycode->isEmpty()) return tr("None"); @@ -46,27 +49,40 @@ void options_dialog::set_disable_translation_state(bool value) }); } -options_dialog::options_dialog(std::function<void(bool)> 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); @@ -87,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); @@ -156,14 +179,25 @@ options_dialog::options_dialog(std::function<void(bool)> pause_keybindings) : 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) @@ -197,7 +231,7 @@ void options_dialog::bind_key(key_opts& kopts, QLabel* label) 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); @@ -218,41 +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 (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 (isHidden()) // close() can return true twice in a row it seems - return; - hide(); - if (!close()) // dialog was closed already - return; + 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() { ui.game_detector->revert(); - if (isHidden()) // close() can return true twice in a row it seems - return; - hide(); - if (!close()) // dialog was closed already - return; - main.b->reload(); - emit closing(); + for (auto* dlg : module_list{ tracker_dialog, proto_dialog, filter_dialog }) + if (dlg) + dlg->reload(); } -void options_dialog::done(int res) +void options_dialog::doOK() { if (isVisible()) { - if (res == QDialog::Accepted) - doOK(); - else - doCancel(); + save(); + close(); } } + +void options_dialog::doCancel() +{ + if (isVisible()) + { + 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 b8de227d..74e8caac 100644 --- a/gui/process_detector.cpp +++ b/gui/process_detector.cpp @@ -56,10 +56,20 @@ void proc_detector_settings::set_is_enabled(bool enabled) }); } +#ifdef __GNUG__ +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + QHash<QString, QString> proc_detector_settings::split_process_names() { QString str = get_game_list(); - QStringList pairs = str.split(RECORD_SEPARATOR, QString::SkipEmptyParts); + 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) @@ -163,6 +173,7 @@ void process_detector::save() void process_detector::revert() { load_rows(); + ui.enabled->setChecked(s.is_enabled()); } void process_detector::add() 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><html><head/><body><p><span style=" font-weight:600;">Center</span> - use current pose as looking perfectly forward.<br/><span style=" font-weight:600;">Toggle</span> - keep looking at same spot until next toggle keypress.<br/><span style=" font-weight:600;">Zero</span> - keep looking forward until next zero keypress.<br/></p></body></html></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 8ef5aa2d..00000000 --- a/gui/settings.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "export.hpp" - -#include "gui/ui_settings-dialog.h" -#include "logic/shortcuts.h" - -#include <functional> - -#include <QObject> -#include <QDialog> -#include <QWidget> - -class OTR_GUI_EXPORT options_dialog : public QDialog -{ - Q_OBJECT -signals: - void closing(); -public: - explicit 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/Output/.gitignore b/installer/Output/.gitignore deleted file mode 100644 index 72e8ffc0..00000000 --- a/installer/Output/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/installer/Output/.gitkeep b/installer/Output/.gitkeep deleted file mode 100644 index e69de29b..00000000 --- a/installer/Output/.gitkeep +++ /dev/null diff --git a/installer/opentrack-installer.iss b/installer/opentrack-installer.iss index e8a20b6c..960ddf7f 100644 --- a/installer/opentrack-installer.iss +++ b/installer/opentrack-installer.iss @@ -20,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 @@ -34,7 +34,6 @@ RestartIfNeededByRun=False InternalCompressLevel=ultra CompressionThreads=4 LZMANumFastBytes=273 -MinVersion=0,5.01sp2 [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" @@ -42,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 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 03d03a83..00000000 --- a/logic/extensions.cpp +++ /dev/null @@ -1,74 +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(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) -{ -#if 0 - auto fun = std::mem_fn(ordinal_to_function[k].ptr); - - for (extension& x : extensions_for_event[k]) - fun(*x.logic, pose); -#else - (void)k; (void)pose; -#endif -} diff --git a/logic/extensions.hpp b/logic/extensions.hpp deleted file mode 100644 index 3368b118..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 '%1'. Proceeding without logging.</source> + <translation>Datei '%1' 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/zh_CN.ts b/logic/lang/zh_CN.ts index cc15f98a..000aff7c 100644 --- a/logic/lang/zh_CN.ts +++ b/logic/lang/zh_CN.ts @@ -1,11 +1,11 @@ <?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 type="unfinished"></translation> + <translation>选择文件名</translation> </message> <message> <source>CSV File (*.csv)</source> @@ -17,35 +17,44 @@ </message> <message> <source>Unable to open file '%1'. Proceeding without logging.</source> - <translation type="unfinished"></translation> + <translation type="unfinished">未能打开文件 '%1'. Proceeding without logging.</translation> </message> </context> <context> <name>runtime_libraries</name> <message> <source>Library load failure</source> - <translation type="unfinished"></translation> + <translation>库加载失败</translation> </message> <message> <source>Error occurred while loading protocol %1 %2 </source> - <translation type="unfinished"></translation> + <translation type="unfinished">加载协议 %1 时发生错误 + +%2 +</translation> </message> <message> <source>Error occurred while loading filter %1 %2 </source> - <translation type="unfinished"></translation> + <translation type="unfinished">加载滤波器 %1 时发生错误 + +%2 +</translation> </message> <message> <source>Error occurred while loading tracker %1 %2 </source> - <translation type="unfinished"></translation> + <translation type="unfinished">加载跟踪器 %1 时发生错误 + +%2 +</translation> </message> <message> <source>Startup failure</source> diff --git a/logic/main-settings.hpp b/logic/main-settings.hpp index 8fef7ea7..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; @@ -65,7 +73,7 @@ struct OTR_LOGIC_EXPORT main_settings final value<bool> tray_start { b, "start-in-tray", false }; value<bool> center_at_startup { b, "center-at-startup", true }; - //value<int> center_method; + 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 }; diff --git a/logic/pipeline.cpp b/logic/pipeline.cpp index e6f82063..2e8efe55 100644 --- a/logic/pipeline.cpp +++ b/logic/pipeline.cpp @@ -15,7 +15,7 @@ #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" @@ -184,8 +184,8 @@ Pose_ reltrans::apply_neck(const rmat& R, int nz, bool disable_tz) const return neck; } -pipeline::pipeline(const Mappings& m, const 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) { } @@ -272,7 +272,7 @@ 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) { if (b.get(f_center | f_held_center)) { @@ -283,34 +283,73 @@ void pipeline::maybe_set_center_pose(const Pose& value, bool own_center_logic) if (own_center_logic) { - center.inv_R = rmat::eye(); - center.T = {}; + center.P = {}; + center.QC = QQuaternion(1,0,0,0); + center.QR = QQuaternion(1,0,0,0); } else { - center.inv_R = euler_to_rmat(Pose_(&value[Yaw]) * (M_PI / 180)).t(); - center.T = Pose_(&value[TX]); + 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::apply_center(Pose value) const +Pose pipeline::apply_center(const centering_state mode, Pose value) const { - { - for (unsigned k = 0; k < 3; k++) - value(k) -= center.T(k); - - Pose_ rot = rmat_to_euler( - euler_to_rmat(Pose_(&value[Yaw]) * (M_PI / 180)) * center.inv_R - ); - for (unsigned k = 0; k < 3; k++) - value(k+3) = rot(k) * 180 / M_PI; + if (mode != center_disabled) + { + for (unsigned k = TX; k <= TZ; k++) + value(k) -= center.P(k); + + QQuaternion q; + QVector3D v; + + 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; + } } for (int i = 0; i < 6; i++) // don't invert after reltrans // inverting here doesn't break centering - if (m(i).opts.invert) + if (m(i).opts.invert_pre) value(i) = -value(i); return value; @@ -352,7 +391,7 @@ Pose pipeline::maybe_apply_filter(const Pose& value) const Pose pipeline::apply_zero_pos(Pose value) const { 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; } @@ -383,7 +422,6 @@ Pose pipeline::apply_reltrans(Pose value, vec6_bool disabled, bool centerp) void pipeline::logic() { using namespace euler; - using EV = event_handler::event_ordinal; logger.write_dt(); logger.reset_dt(); @@ -396,7 +434,6 @@ void pipeline::logic() { Pose tmp; libs.pTracker->data(tmp); - ev.run_events(EV::ev_raw, tmp); newpose = tmp; } @@ -407,15 +444,14 @@ void pipeline::logic() { 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); // we must proceed with all the filtering since the filter // needs fresh values to prevent deconvergence if (center_ordered) @@ -427,7 +463,6 @@ void pipeline::logic() } { - ev.run_events(EV::ev_before_mapping, value); // CAVEAT rotation only, due to reltrans for (int i = 3; i < 6; i++) value(i) = map(value(i), m(i)); @@ -442,14 +477,13 @@ void pipeline::logic() nan_check(value); } - if (!hold_ordered) - goto ok; + goto ok; error: { QMutexLocker foo(&mtx); - value = output_pose; + value = last_value; raw = raw_6dof; // for widget last value display @@ -465,9 +499,15 @@ ok: 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); + 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); @@ -564,8 +604,7 @@ void pipeline::run() backlog_time = {}; } - const int sleep_ms = (int)clamp(interval - backlog_time, - ms{0}, ms{10}).count(); + const int sleep_ms = (int)std::clamp(interval - backlog_time, ms{0}, ms{10}).count(); #ifdef DEBUG_TIMINGS debug_timings(backlog_time.count(), sleep_ms); diff --git a/logic/pipeline.hpp b/logic/pipeline.hpp index 7775054e..a539525d 100644 --- a/logic/pipeline.hpp +++ b/logic/pipeline.hpp @@ -8,7 +8,6 @@ #include "compat/euler.hpp" #include "compat/enum-operators.hpp" #include "runtime-libraries.hpp" -#include "extensions.hpp" #include "spline/spline.hpp" #include "main-settings.hpp" @@ -20,6 +19,7 @@ #include <atomic> #include <cmath> +#include <QQuaternion> #include "export.hpp" @@ -84,10 +84,9 @@ class OTR_LOGIC_EXPORT pipeline : private QThread mutable QMutex mtx; main_settings s; const Mappings& m; - event_handler& ev; Timer t; - Pose output_pose, raw_6dof; + Pose output_pose, raw_6dof, last_value; Pose newpose; runtime_libraries const& libs; @@ -99,8 +98,9 @@ class OTR_LOGIC_EXPORT pipeline : private QThread reltrans rel; struct { - rmat inv_R = rmat::eye(); - Pose_ T; + Pose P; + QQuaternion QC = QQuaternion(1,0,0,0); + QQuaternion QR = QQuaternion(1,0,0,0); } center; time_units::ms backlog_time {}; @@ -111,8 +111,8 @@ class OTR_LOGIC_EXPORT pipeline : private QThread 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 apply_center(Pose value) 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, bool centerp); @@ -123,7 +123,7 @@ class OTR_LOGIC_EXPORT pipeline : private QThread bits b; public: - pipeline(const Mappings& m, const runtime_libraries& libs, event_handler& ev, TrackLogger& logger); + pipeline(const Mappings& m, const runtime_libraries& libs, TrackLogger& logger); ~pipeline() override; void raw_and_mapped_pose(double* mapped, double* raw) const; diff --git a/logic/shortcuts.cpp b/logic/shortcuts.cpp index 377fa3e5..df21f7d2 100644 --- a/logic/shortcuts.cpp +++ b/logic/shortcuts.cpp @@ -51,6 +51,12 @@ void Shortcuts::bind_shortcut(K& key, const key_opts& k, bool held) int idx = 0; QKeySequence code(QKeySequence::UnknownKey); + if (k.guid == QStringLiteral("mouse")) + { + key.guid = k.guid; + key.keycode = k.button; + key.held = held; + } if (!k.guid->isEmpty()) { key.guid = k.guid; @@ -70,7 +76,7 @@ void Shortcuts::bind_shortcut(K& key, const key_opts& k, bool held) code != QKeySequence{ QKeySequence::UnknownKey } && win_key::from_qt(code, idx, mods)) { - key.guid = ""; + key.guid = QString{}; key.keycode = idx; key.held = held; key.ctrl = !!(mods & Qt::ControlModifier); @@ -83,26 +89,27 @@ void Shortcuts::bind_shortcut(K& key, const key_opts& k, bool held) #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); } } diff --git a/logic/state.cpp b/logic/state.cpp index afdf5b12..dc5e5a36 100644 --- a/logic/state.cpp +++ b/logic/state.cpp @@ -1,5 +1,5 @@ #include "state.hpp" - +#include "opentrack/defs.hpp" #include <iterator> using dylib_ptr = Modules::dylib_ptr; @@ -22,13 +22,19 @@ std::tuple<dylib_ptr, int> State::module_by_name(const QString& name, dylib_list State::State(const QString& library_path) : modules(library_path), - ev(modules.extensions()), - pose(s.all_axis_opts) + pose(s.all_axis_opts), + library_path{library_path} {} dylib_ptr State::current_tracker() { - auto [ptr, idx] = module_by_name(m.tracker_dll, modules.trackers()); + 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; } @@ -40,6 +46,12 @@ dylib_ptr State::current_protocol() dylib_ptr State::current_filter() { - auto [ptr, idx] = module_by_name(m.filter_dll, modules.filters()); + 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 abce1daf..7fc06a5c 100644 --- a/logic/state.hpp +++ b/logic/state.hpp @@ -12,7 +12,6 @@ #include "api/plugin-support.hpp" #include "main-settings.hpp" #include "mappings.hpp" -#include "extensions.hpp" #include "work.hpp" #include "export.hpp" @@ -32,9 +31,9 @@ struct OTR_LOGIC_EXPORT State 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/win32-shortcuts.cpp b/logic/win32-shortcuts.cpp index c2920071..cb4f99b3 100644 --- a/logic/win32-shortcuts.cpp +++ b/logic/win32-shortcuts.cpp @@ -41,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 }, @@ -63,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 }, diff --git a/logic/work.cpp b/logic/work.cpp index 302414b8..8c6a3a62 100644 --- a/logic/work.cpp +++ b/logic/work.cpp @@ -36,12 +36,8 @@ 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. - return {}; - } - else + + if (!filename.isEmpty()) { auto logger = std::make_unique<TrackLoggerCSV>(*s.tracklogging_filename); @@ -61,10 +57,10 @@ std::unique_ptr<TrackLogger> Work::make_logger(main_settings &s) } -Work::Work(const Mappings& m, event_handler& ev, QFrame* frame, +Work::Work(const Mappings& m, QFrame* frame, const dylibptr& tracker, const dylibptr& filter, const dylibptr& proto) : libs(frame, tracker, filter, proto), - pipeline_{ m, libs, ev, *logger } + pipeline_{ m, libs, *logger } { if (!is_ok()) return; diff --git a/logic/work.hpp b/logic/work.hpp index 82449e47..ef839257 100644 --- a/logic/work.hpp +++ b/logic/work.hpp @@ -45,21 +45,21 @@ public: std::vector<key_tuple> keys { // third argument means "keydown only" - key_tuple(s.key_center1, [&](bool x) { pipeline_.set_held_center(x); }, false), - key_tuple(s.key_center2, [&](bool x) { pipeline_.set_held_center(x); }, false), + 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), - key_tuple(s.key_toggle1, [&](bool) { pipeline_.toggle_enabled(); }, true), - key_tuple(s.key_toggle2, [&](bool) { pipeline_.toggle_enabled(); }, true), - key_tuple(s.key_toggle_press1, [&](bool x) { pipeline_.set_enabled(!x); }, false), - key_tuple(s.key_toggle_press2, [&](bool x) { pipeline_.set_enabled(!x); }, false), + 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, [&](bool) { pipeline_.toggle_zero(); }, true), - key_tuple(s.key_zero2, [&](bool) { pipeline_.toggle_zero(); }, true), - key_tuple(s.key_zero_press1, [&](bool x) { pipeline_.set_zero(x); }, false), - key_tuple(s.key_zero_press2, [&](bool x) { pipeline_.set_zero(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, event_handler& ev, QFrame* frame, + Work(const Mappings& m, QFrame* frame, const dylibptr& tracker, const dylibptr& filter, const dylibptr& proto); void reload_shortcuts(); bool is_ok() const; 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 Binary files differnew file mode 100644 index 00000000..e3a398c0 --- /dev/null +++ b/macosx/dmgbackground.png 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/main-window/CMakeLists.txt b/main-window/CMakeLists.txt deleted file mode 100644 index c9228536..00000000 --- a/main-window/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -if(opentrack_maintainer-mode) - otr_module(main-window BIN NO-INSTALL) - foreach(k user-interface logic pose-widget migration spline) - target_link_libraries(${self} opentrack-${k}) - endforeach() -endif() diff --git a/main-window/export.hpp b/main-window/export.hpp deleted file mode 100644 index 184cf035..00000000 --- a/main-window/export.hpp +++ /dev/null @@ -1,11 +0,0 @@ -// generates export.hpp for each module from compat/linkage.hpp - -#pragma once - -#include "compat/linkage-macros.hpp" - -#ifdef BUILD_MAIN_WINDOW -# define OTR_MAIN_EXPORT OTR_GENERIC_EXPORT -#else -# define OTR_MAIN_EXPORT OTR_GENERIC_IMPORT -#endif diff --git a/main-window/lang/nl_NL.ts b/main-window/lang/nl_NL.ts deleted file mode 100644 index 6401616d..00000000 --- a/main-window/lang/nl_NL.ts +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!DOCTYPE TS> -<TS version="2.1"> -</TS> diff --git a/main-window/lang/ru_RU.ts b/main-window/lang/ru_RU.ts deleted file mode 100644 index 6401616d..00000000 --- a/main-window/lang/ru_RU.ts +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!DOCTYPE TS> -<TS version="2.1"> -</TS> diff --git a/main-window/lang/stub.ts b/main-window/lang/stub.ts deleted file mode 100644 index 6401616d..00000000 --- a/main-window/lang/stub.ts +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!DOCTYPE TS> -<TS version="2.1"> -</TS> diff --git a/main-window/lang/zh_CN.ts b/main-window/lang/zh_CN.ts deleted file mode 100644 index 6401616d..00000000 --- a/main-window/lang/zh_CN.ts +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!DOCTYPE TS> -<TS version="2.1"> -</TS> diff --git a/main-window/mixin-traits.cpp b/main-window/mixin-traits.cpp deleted file mode 100644 index b74a6f7a..00000000 --- a/main-window/mixin-traits.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#define MIXIN_TRAIT_TESTS - -#ifdef MIXIN_TRAIT_TESTS -# include "mixin-traits.hpp" - -// the `impl' class provides a cast template through the CRTP pattern. -// mixins don't do direct inheritance on themselves, -// that's what mixin_traits::depends is for. - -namespace mixins::traits_detail { - -struct A {}; -struct B {}; -struct C {}; -struct D {}; - -template<> struct mixin_traits<A> -{ - using depends = tuple<>; -}; - -template<> struct mixin_traits<B> -{ - using depends = tuple<A>; -}; - -template<> struct mixin_traits<C> -{ - using depends = tuple<A>; -}; - -template<> struct mixin_traits<D> -{ - using depends = tuple<C>; -}; - -extern void test1(); - -void test1() -{ - struct U : B, A {}; - struct V : D {}; - struct W : C, A {}; - struct Q : virtual W, virtual D {}; - -//#define SHOULD_NOT_COMPILE -#ifdef SHOULD_NOT_COMPILE - (void)impl<Q, W>{}; // W not a mixin - (void)impl<V, A>{}; // A - (void)impl<V, D>{}; // D => C => A - (void)impl<V, D>{}; // D => C => A - (void)impl<W, C, B>{}; // B -#else - (void)impl<U, B>{}; - (void)impl<W, C>{}; - (void)impl<Q, D, A>{}; -#endif -} - -} // ns mixins::traits_detail - -#endif diff --git a/main-window/mixin-traits.hpp b/main-window/mixin-traits.hpp deleted file mode 100644 index 45df7fdb..00000000 --- a/main-window/mixin-traits.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "compat/meta.hpp" - -#include <type_traits> - -namespace mixins::traits_detail { - - using namespace meta; - - template<typename... xs> using tuple = tuple_<xs...>; - - template<typename t> - struct mixin_traits { - // implement this! - //using depends = tuple<>; - }; - - template<typename klass, typename...> struct check_depends_; - - template<typename klass> - struct check_depends_<klass> : std::true_type - { - }; - - template<typename klass, typename x, typename... xs> - struct check_depends_<klass, x, xs...> : - std::bool_constant< - std::is_base_of_v<x, klass> && - lift_v<check_depends_, cons<klass, typename mixin_traits<x>::depends>> && - check_depends_<klass, xs...>::value - > - { - }; - - template<typename klass, typename... xs> - struct impl - { - static_assert(lift<check_depends_, tuple<klass, xs...>>::value, - "class must inherit dependent mixins"); - }; -} // ns mixins::traits_detail diff --git a/main-window/mixins.hpp b/main-window/mixins.hpp deleted file mode 100644 index b85e6498..00000000 --- a/main-window/mixins.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "export.hpp" - -// XXX TODO add is_base_of and void_t stuff - -#define OTR_MIXIN_NS(name) \ - mixins :: detail :: name - -#define OTR_DECLARE_MIXIN(name) \ - namespace mixins { \ - using name = :: OTR_MIXIN_NS(name) :: name; \ - } diff --git a/main-window/module-mixin.cpp b/main-window/module-mixin.cpp deleted file mode 100644 index 18b2867d..00000000 --- a/main-window/module-mixin.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "module-mixin.hpp" - -#include <algorithm> - -namespace OTR_MIXIN_NS(module_mixin) { - -std::tuple<dylib_ptr, int> -module_mixin::module_by_name(const QString& name, const dylib_list& list) const -{ - 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)) }; -} - -//static -void show_window(QWidget& d, bool fresh) -{ - if (fresh) - { - d.setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | d.windowFlags()); - d.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - d.show(); - d.adjustSize(); - d.raise(); - } - else - { - d.show(); - d.raise(); - } -} - -template<typename t, typename F> -static bool mk_window_common(std::unique_ptr<t>& d, F&& fun) -{ - bool fresh = false; - - if (!d) - d = fun(), fresh = !!d; - - if (d) - show_window(*d, fresh); - - return fresh; -} - -template<typename t> -static bool mk_dialog(std::unique_ptr<t>& place, const std::shared_ptr<dylib>& lib) -{ - using u = std::unique_ptr<t>; - - return mk_window_common(place, [&] { - if (lib && lib->Dialog) - return u{ (t*)lib->Dialog() }; - else - return u{}; - }); -} - -dylib_ptr module_mixin::current_tracker() -{ - auto [ptr, idx] = module_by_name(s.tracker_dll, modules.trackers()); - return ptr; -} - -dylib_ptr module_mixin::current_protocol() -{ - auto [ptr, idx] = module_by_name(s.protocol_dll, modules.protocols()); - return ptr; -} - -dylib_ptr module_mixin::current_filter() -{ - auto [ptr, idx] = module_by_name(s.filter_dll, modules.filters()); - return ptr; -} - -void module_mixin::show_tracker_settings_() -{ - if (mk_dialog(tracker_dialog, current_tracker()) && state.work && state.work->libs.pTracker) - tracker_dialog->register_tracker(state.work->libs.pTracker.get()); - if (tracker_dialog) - QObject::connect(tracker_dialog.get(), &ITrackerDialog::closing, - &fuzz, [this] { tracker_dialog = nullptr; }); -} - -void module_mixin::show_proto_settings_() -{ - if (mk_dialog(proto_dialog, current_protocol()) && state.work && state.work->libs.pProtocol) - proto_dialog->register_protocol(state.work->libs.pProtocol.get()); - if (proto_dialog) - QObject::connect(proto_dialog.get(), &IProtocolDialog::closing, - &fuzz, [this] { proto_dialog = nullptr; }); -} - -void module_mixin::show_filter_settings_() -{ - if (mk_dialog(filter_dialog, current_filter()) && state.work && state.work->libs.pFilter) - filter_dialog->register_filter(state.work->libs.pFilter.get()); - if (filter_dialog) - QObject::connect(filter_dialog.get(), &IFilterDialog::closing, - &fuzz, [this] { filter_dialog = nullptr; }); -} - -// this template function must go to a separate function like "options_mixin". -template<typename t, typename... Args> -static bool mk_window(std::unique_ptr<t>& place, Args&&... params) -{ - return mk_window_common(place, [&] { - return std::make_unique<t>(params...); - }); -} - -module_mixin::module_mixin() = default; -module_mixin::~module_mixin() = default; - -} // ns diff --git a/main-window/module-mixin.hpp b/main-window/module-mixin.hpp deleted file mode 100644 index cde0484c..00000000 --- a/main-window/module-mixin.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "mixins.hpp" -#include "compat/library-path.hpp" -#include "api/plugin-api.hpp" -#include "logic/extensions.hpp" -#include "logic/state.hpp" -#include "logic/main-settings.hpp" - -#include <memory> -#include <utility> - -#include <QObject> - -namespace OTR_MIXIN_NS(module_mixin) { - -using namespace options; - -using dylib_ptr = Modules::dylib_ptr; -using dylib_list = Modules::dylib_list; - -struct OTR_MAIN_EXPORT module_mixin -{ - module_mixin(); - virtual ~module_mixin(); - - std::unique_ptr<ITrackerDialog> tracker_dialog; - std::unique_ptr<IProtocolDialog> proto_dialog; - std::unique_ptr<IFilterDialog> filter_dialog; - - std::tuple<dylib_ptr, int> module_by_name(const QString& name, const dylib_list& list) const; - - dylib_ptr current_tracker(); - dylib_ptr current_protocol(); - dylib_ptr current_filter(); - - void show_tracker_settings_(); - void show_proto_settings_(); - void show_filter_settings_(); - -private: - Modules modules { OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH }; - event_handler ev { modules.extensions() }; - module_settings s; - State state { OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH }; - - QObject fuzz; -}; - -} - -OTR_DECLARE_MIXIN(module_mixin) 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 b2dfac6d..effeddcb 100644 --- a/migration/CMakeLists.txt +++ b/migration/CMakeLists.txt @@ -1,5 +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/ext-falcon-bms-linear-acc/lang/zh_CN.ts b/migration/lang/de_DE.ts index 6401616d..1552582e 100644 --- a/ext-falcon-bms-linear-acc/lang/zh_CN.ts +++ b/migration/lang/de_DE.ts @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<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 4ddb2c8c..f4b1739b 100644 --- a/migration/migration.cpp +++ b/migration/migration.cpp @@ -26,14 +26,25 @@ using namespace options::globals; namespace migrations::detail { -static std::vector<mptr> migration_list; -static std::vector<mfun> migration_thunks; + +std::vector<mptr>& migrator::migration_list() +{ + static std::vector<mptr> v; + return v; +} + +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 (mptr const& m2 : migration_list) + for (mptr const& m2 : migration_list()) if (m2->unique_date() == date) std::abort(); @@ -65,35 +76,35 @@ void migrator::register_migration(mptr const& m) if (day_ < 1 || day_ > 31) abort(); - migration_list.push_back(m); + migration_list().push_back(m); } void migrator::eval_thunks() { - for (auto& fun : migration_thunks) + for (auto& fun : migration_thunks()) { mptr m = fun(); register_migration(m); } - if (!migration_thunks.empty()) + if (!migration_thunks().empty()) sort_migrations(); - migration_thunks.clear(); + migration_thunks().clear(); } void migrator::add_migration_thunk(mfun& thunk) { - migration_thunks.push_back(thunk); + migration_thunks().push_back(thunk); } std::vector<mptr>& migrator::migrations() { eval_thunks(); - return migration_list; + return migration_list(); } void migrator::sort_migrations() { - std::sort(migration_list.begin(), migration_list.end(), + std::sort(migration_list().begin(), migration_list().end(), [](const mptr x, const mptr y) { return x->unique_date() < y->unique_date(); }); diff --git a/migration/migration.hpp b/migration/migration.hpp index a3035247..7fc18c97 100644 --- a/migration/migration.hpp +++ b/migration/migration.hpp @@ -43,6 +43,9 @@ namespace detail { static void set_last_migration_time(const QString& val); static int to_int(const QString& str, bool& ok); + + static std::vector<mptr>& migration_list(); + static std::vector<mfun>& migration_thunks(); }; template<typename t> diff --git a/opentrack/CMakeLists.txt b/opentrack/CMakeLists.txt index a8829aa5..0fd72475 100644 --- a/opentrack/CMakeLists.txt +++ b/opentrack/CMakeLists.txt @@ -1,3 +1,8 @@ +if(MSVC) + add_compile_options(-EHsc) + add_definitions(-D_HAS_EXCEPTIONS=1) +endif() + otr_module(executable EXECUTABLE BIN) set_target_properties(opentrack-executable PROPERTIES @@ -6,4 +11,15 @@ set_target_properties(opentrack-executable PROPERTIES 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: + +"%1"%2 + +Exiting now.</source> + <translation>Überprüfe die Berechtigungen des .ini-Ordners: + +"%1"%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'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'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/opentrack/main-window.cpp b/opentrack/main-window.cpp index cd715216..20506d81 100644 --- a/opentrack/main-window.cpp +++ b/opentrack/main-window.cpp @@ -13,17 +13,30 @@ #include "migration/migration.hpp" #include "compat/check-visible.hpp" #include "compat/sleep.hpp" -#include "compat/macros.hpp" +#include "compat/macros.h" #include "compat/library-path.hpp" #include "compat/math.hpp" #include "compat/sysexits.hpp" +#include "opentrack/defs.hpp" -#include <algorithm> +#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; @@ -34,11 +47,12 @@ main_window::main_window() : State(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH) { ui.setupUi(this); -#if !defined _WIN32 && !defined __APPLE__ +#if !defined _WIN32 annoy_if_root(); #endif - setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | windowFlags()); + adjustSize(); + setWindowFlag(Qt::MSWindowsFixedSizeDialogHint); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); init_profiles(); @@ -47,7 +61,7 @@ main_window::main_window() : State(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH) init_shortcuts(); init_tray_menu(); - setVisible(start_in_tray()); + setVisible(!start_in_tray()); ensure_tray(); connect(&pose_update_timer, &QTimer::timeout, @@ -55,6 +69,14 @@ main_window::main_window() : State(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH) 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() @@ -74,23 +96,31 @@ void main_window::init_dylibs() 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, [&](const QString&) { pTrackerDialog = nullptr; }); + this, [this](const QString&) { pTrackerDialog = nullptr; if (options_widget) options_widget->tracker_module_changed(); }); +#endif connect(ui.iconcomboProtocol, &QComboBox::currentTextChanged, - this, [&](const QString&) { pProtocolDialog = nullptr; }); + 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, [&](const QString&) { pFilterDialog = nullptr; }); + 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, @@ -112,9 +142,13 @@ void main_window::init_dylibs() }; 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) @@ -138,6 +172,7 @@ void main_window::init_dylibs() void main_window::init_profiles() { + copy_presets(); refresh_profile_list(); // implicitly created by `ini_directory()' if (ini_directory().isEmpty() || !QDir(ini_directory()).isReadable()) @@ -176,7 +211,7 @@ void main_window::init_tray_menu() 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); }); + QObject::connect(&menu_action_show, &QAction::triggered, this, [this] { toggle_restore_from_tray(QSystemTrayIcon::Trigger); }); tray_menu.addAction(&menu_action_show); tray_menu.addSeparator(); @@ -205,7 +240,7 @@ void main_window::init_tray_menu() 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); + QObject::connect(&menu_action_options, &QAction::triggered, this, [this] { show_options_dialog(true); }); tray_menu.addAction(&menu_action_options); tray_menu.addSeparator(); @@ -222,10 +257,14 @@ 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, &main_window::show_options_dialog); + 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_); } @@ -274,20 +313,17 @@ bool main_window::profile_name_from_dialog(QString& 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(); - constexpr int inc = 25, max = 1000; - - for (int k = 0; k < max; k += inc) - { - QEventLoop ev; - ev.processEvents(); - portable::sleep(inc); - } + portable::sleep(1000); } exit(); @@ -303,7 +339,7 @@ void main_window::create_empty_profile() QString name; if (profile_name_from_dialog(name)) { - QFile(ini_combine(name)).open(QFile::ReadWrite); + (void)create_profile_from_preset(name); refresh_profile_list(); if (profile_list.contains(name)) @@ -382,9 +418,14 @@ void main_window::update_button_state(bool running, bool inertialp) 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) { @@ -393,6 +434,7 @@ void main_window::update_button_state(bool running, bool inertialp) else { ui.video_frame_label->setPixmap(QPixmap(":/images/no-feed.png")); } +#endif } void main_window::start_tracker_() @@ -400,7 +442,17 @@ void main_window::start_tracker_() if (work) return; - work = std::make_shared<Work>(pose, ev, ui.video_frame, current_tracker(), current_protocol(), current_filter()); +#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()) { @@ -414,19 +466,27 @@ void main_window::start_tracker_() } if (pTrackerDialog) - pTrackerDialog->register_tracker(work->libs.pTracker.get()); + pTrackerDialog->register_tracker(&*work->libs.pTracker); - if (pFilterDialog) - pFilterDialog->register_filter(work->libs.pFilter.get()); + if (pFilterDialog && work->libs.pFilter) + pFilterDialog->register_filter(&*work->libs.pFilter); if (pProtocolDialog) - pProtocolDialog->register_protocol(work->libs.pProtocol.get()); + 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(50); + pose_update_timer.start(1000/30); // NB check valid since SelectedLibraries ctor called // trackers take care of layout state updates - const bool is_inertial = ui.video_frame->layout() == nullptr; + const bool is_inertial = frame->layout() == nullptr; update_button_state(true, is_inertial); ui.btnStopTracker->setFocus(); @@ -437,12 +497,22 @@ 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(); @@ -469,20 +539,29 @@ 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; @@ -523,15 +602,57 @@ void main_window::show_pose() 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.setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | d.windowFlags()); - d.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - d.show(); + d.setWindowFlag(Qt::MSWindowsFixedSizeDialogHint); d.adjustSize(); + d.setFixedSize(d.size()); + d.show(); #ifdef __APPLE__ d.raise(); #endif @@ -547,83 +668,130 @@ static void show_window(QWidget& d, bool fresh) } } -template<typename t, typename F> -static bool mk_window_common(std::unique_ptr<t>& d, F&& fun) +template<typename t, typename... Args> +static bool mk_window(std::unique_ptr<t>& d, bool show, Args&&... params) { bool fresh = false; - if (!d) - d = fun(), fresh = !!d; + if (!(d && d->isVisible())) + { + d = std::make_unique<t>(std::forward<Args>(params)...); + fresh = !!d; + } if (d) - show_window(*d, fresh); + { + if (show && !d->embeddable()) + show_window(*d, fresh); + } return fresh; } -template<typename t, typename... Args> -static bool mk_window(std::unique_ptr<t>& place, Args&&... params) +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)()) { - return mk_window_common(place, [&] { - return std::make_unique<t>(params...); - }); -} + if (!lib || !lib->Dialog) + return; -template<typename t> -static bool mk_dialog(std::unique_ptr<t>& place, const std::shared_ptr<dylib>& lib) -{ - using u = std::unique_ptr<t>; + bool fresh = !(dialog && dialog->isVisible()); + if (fresh) + dialog = std::unique_ptr<Dialog>{(Dialog*)lib->Dialog()}; + bool embed = dialog->embeddable() && win->module_tabs_enabled(); - return mk_window_common(place, [&] { - if (lib && lib->Dialog) - return u{ (t*)lib->Dialog() }; - else - return u{}; - }); + 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() +void main_window::show_tracker_settings_(bool show) { - if (mk_dialog(pTrackerDialog, current_tracker()) && work && work->libs.pTracker) - pTrackerDialog->register_tracker(work->libs.pTracker.get()); - if (pTrackerDialog) - QObject::connect(pTrackerDialog.get(), &ITrackerDialog::closing, - this, [this] { pTrackerDialog = nullptr; }); + 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() +void main_window::show_proto_settings_(bool show) { - if (mk_dialog(pProtocolDialog, current_protocol()) && work && work->libs.pProtocol) - pProtocolDialog->register_protocol(work->libs.pProtocol.get()); - if (pProtocolDialog) - QObject::connect(pProtocolDialog.get(), &IProtocolDialog::closing, - this, [this] { pProtocolDialog = nullptr; }); + 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() +void main_window::show_filter_settings_(bool show) { - if (mk_dialog(pFilterDialog, current_filter()) && work && work->libs.pFilter) - pFilterDialog->register_filter(work->libs.pFilter.get()); - if (pFilterDialog) - QObject::connect(pFilterDialog.get(), &IFilterDialog::closing, - this, [this] { pFilterDialog = nullptr; }); + 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() +void main_window::show_options_dialog(bool show) { - if (mk_window(options_widget, [&](bool flag) { set_keys_enabled(!flag); })) + if (options_widget && options_widget->isVisible()) { - // 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); + 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, pose); + mk_window(mapping_widget, true, pose); } void main_window::exit(int status) @@ -652,14 +820,25 @@ 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)) + if (!profile_list.contains(new_name_)) { - new_name = OPENTRACK_DEFAULT_PROFILE; + 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) @@ -713,10 +892,8 @@ void main_window::ensure_tray() tray->setContextMenu(&tray_menu); tray->show(); - connect(tray.get(), - &QSystemTrayIcon::activated, - this, - &main_window::toggle_restore_from_tray); + connect(&*tray, &QSystemTrayIcon::activated, + this, &main_window::toggle_restore_from_tray); } QApplication::setQuitOnLastWindowClosed(false); @@ -873,6 +1050,56 @@ void main_window::toggle_tracker_() 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() diff --git a/opentrack/main-window.hpp b/opentrack/main-window.hpp index 9ffb7019..1dcbd0eb 100644 --- a/opentrack/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" @@ -20,21 +21,16 @@ #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 <tuple> #include <memory> #include "ui_main-window.h" @@ -55,6 +51,11 @@ class main_window final : public QMainWindow, private State 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; @@ -78,11 +79,12 @@ class main_window final : public QMainWindow, private State bool exiting_already { false }; - qt_sig::nullary start_tracker { this, &main_window::start_tracker_, Qt::QueuedConnection }; - qt_sig::nullary stop_tracker { this, &main_window::stop_tracker_, Qt::QueuedConnection }; - qt_sig::nullary toggle_tracker { this, &main_window::toggle_tracker_, Qt::QueuedConnection }; - qt_sig::nullary restart_tracker { this, &main_window::restart_tracker_, Qt::QueuedConnection }; + 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(); @@ -103,11 +105,14 @@ class main_window final : public QMainWindow, private State void closeEvent(QCloseEvent *event) override; bool event(QEvent *event) override; - void show_tracker_settings(); - void show_proto_settings(); - void show_filter_settings(); + 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_options_dialog(); + void show_options_dialog(bool show); void show_mapping_window(); void show_pose(); @@ -119,12 +124,14 @@ class main_window final : public QMainWindow, private State void restart_tracker_(); void toggle_tracker_(); + [[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(); @@ -136,6 +143,7 @@ class main_window final : public QMainWindow, private State bool start_in_tray(); void save_modules(); + bool module_tabs_enabled() const; void exit(int status = EXIT_SUCCESS); diff --git a/opentrack/main-window.ui b/opentrack/main-window.ui index a30ef9ed..32c9f57a 100644 --- a/opentrack/main-window.ui +++ b/opentrack/main-window.ui @@ -7,16 +7,22 @@ <rect> <x>0</x> <y>0</y> - <width>818</width> - <height>666</height> + <width>655</width> + <height>502</height> </rect> </property> <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <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> @@ -27,15 +33,12 @@ </property> <widget class="QWidget" name="content"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <layout class="QHBoxLayout" name="horizontalLayout_5"> - <property name="spacing"> - <number>0</number> - </property> + <layout class="QGridLayout" name="gridLayout_2"> <property name="leftMargin"> <number>0</number> </property> @@ -43,26 +46,798 @@ <number>0</number> </property> <property name="rightMargin"> - <number>6</number> + <number>9</number> </property> <property name="bottomMargin"> - <number>6</number> + <number>9</number> </property> - <item> - <widget class="QFrame" name="frame"> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> + <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="frameShadow"> - <enum>QFrame::Raised</enum> + <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> - <layout class="QVBoxLayout" name="verticalLayout_5"> + <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> @@ -75,141 +850,143 @@ <property name="bottomMargin"> <number>0</number> </property> - <item alignment="Qt::AlignLeft|Qt::AlignTop"> - <widget class="QWidget" name="top" native="true"> - <layout class="QHBoxLayout" name="horizontalLayout_2"> + <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>0</number> + <number>4</number> </property> <property name="topMargin"> <number>0</number> </property> <property name="rightMargin"> - <number>0</number> + <number>4</number> </property> <property name="bottomMargin"> - <number>4</number> + <number>0</number> </property> - <item alignment="Qt::AlignLeft|Qt::AlignTop"> - <widget class="QFrame" name="video_feed"> + <item> + <widget class="QToolButton" name="profile_button"> + <property name="enabled"> + <bool>true</bool> + </property> <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <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>320</width> - <height>240</height> + <width>245</width> + <height>0</height> </size> </property> - <property name="maximumSize"> + <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>320</width> - <height>240</height> + <width>80</width> + <height>24</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"> + <widget class="QPushButton" name="btnEditCurves"> <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <sizepolicy hsizetype="Expanding" vsizetype="Minimum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="minimumSize"> + <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>320</width> - <height>240</height> + <width>80</width> + <height>24</height> </size> </property> </widget> @@ -218,1184 +995,329 @@ </widget> </item> <item> - <widget class="QFrame" name="top_display"> + <widget class="QGroupBox" name="groupStartStop"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> - <horstretch>0</horstretch> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum"> + <horstretch>4</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> </property> - <property name="lineWidth"> - <number>0</number> + <property name="title"> + <string>Tracking</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout_3"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> <property name="spacing"> - <number>6</number> + <number>7</number> </property> <property name="leftMargin"> - <number>5</number> + <number>4</number> </property> <property name="topMargin"> - <number>0</number> + <number>4</number> </property> <property name="rightMargin"> - <number>0</number> + <number>4</number> </property> <property name="bottomMargin"> - <number>6</number> + <number>4</number> </property> <item> - <widget class="QGroupBox" name="box_raw_headpose"> + <widget class="QToolButton" name="btnStartTracker"> <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="title"> - <string>Raw tracker data</string> + <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> - <layout class="QGridLayout" name="gridLayout_12"> - <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>6</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="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="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="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="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="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="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="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="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="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="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="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> - <widget class="QGroupBox" name="box_mapped_headpose"> + <widget class="QToolButton" name="btnStopTracker"> + <property name="enabled"> + <bool>false</bool> + </property> <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="title"> - <string>Game data</string> + <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> - <layout class="QGridLayout" name="gridLayout_14"> - <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>6</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="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="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="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="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="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="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="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="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="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="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="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="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> + </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="QFrame" name="bottom_controls"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <widget class="QGroupBox" name="groupTracker"> + <property name="title"> + <string>Input</string> </property> - <layout class="QGridLayout" name="gridLayout_2"> + <layout class="QGridLayout" name="gridLayout"> <property name="leftMargin"> - <number>6</number> + <number>4</number> </property> <property name="topMargin"> - <number>0</number> + <number>4</number> </property> <property name="rightMargin"> - <number>0</number> + <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> - <property name="spacing"> + <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="QFrame" name="groupWindows"> + <widget class="QToolButton" name="btnShowServerControls"> + <property name="enabled"> + <bool>true</bool> + </property> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>4</horstretch> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="minimumSize"> - <size> - <width>400</width> - <height>0</height> - </size> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <stylestrategy>PreferAntialias</stylestrategy> + <kerning>false</kerning> + </font> </property> - <property name="lineWidth"> - <number>0</number> + <property name="focusPolicy"> + <enum>Qt::ClickFocus</enum> + </property> + <property name="text"> + <string>🔨</string> + </property> + <property name="flat" stdset="0"> + <bool>false</bool> </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> + </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="QFrame" name="frame_2"> + <widget class="QComboBox" name="iconcomboFilter"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>4</horstretch> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="minimumSize"> - <size> - <width>400</width> - <height>0</height> - </size> + <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="frameShape"> - <enum>QFrame::NoFrame</enum> + <property name="text"> + <string>🔨</string> </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> + <property name="flat" stdset="0"> + <bool>false</bool> </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> @@ -1414,14 +1336,6 @@ <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> diff --git a/options/base-value.cpp b/options/base-value.cpp index d4ec4b6c..950629d0 100644 --- a/options/base-value.cpp +++ b/options/base-value.cpp @@ -1,7 +1,20 @@ #include "base-value.hpp" +#include <QThread> using namespace options; +//#define OTR_TRACE_NOTIFY + +const bool value_::TRACE_NOTIFY = +#ifdef OTR_TRACE_NOTIFY + true; +#else + [] { + auto b = qgetenv("OTR_TRACE_NOTIFY"); + return !b.isEmpty() && b != "0"; + }(); +#endif + value_::value_(bundle const& b, const QString& name) noexcept : b(b), self_name(name) { @@ -12,3 +25,9 @@ value_::~value_() { b->on_value_destructed(this); } + +void value_::maybe_trace(const char* str) const +{ + if (TRACE_NOTIFY) + qDebug().noquote() << str << QThread::currentThreadId() << b->name() << self_name << get_variant(); +} diff --git a/options/base-value.hpp b/options/base-value.hpp index 722107a4..5317191b 100644 --- a/options/base-value.hpp +++ b/options/base-value.hpp @@ -6,7 +6,6 @@ #include "metatype.hpp" #include "export.hpp" -#include "compat/macros.hpp" #include "value-traits.hpp" #include <utility> @@ -26,6 +25,7 @@ class OTR_OPTIONS_EXPORT value_ : public QObject { Q_OBJECT + template<typename t> using cv_qualified = detail::cv_qualified<t>; template<typename t> using signal_sig = void(value_::*)(cv_qualified<t>) const; @@ -40,6 +40,8 @@ public: return static_cast<signal_sig<t>>(&value_::valueChanged); } + static const bool TRACE_NOTIFY; + signals: OTR_OPTIONS_SIGNAL(double); OTR_OPTIONS_SIGNAL(float); @@ -65,6 +67,8 @@ protected: 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) { @@ -91,6 +95,7 @@ public slots: virtual void set_to_default() noexcept = 0; virtual void notify() const = 0; + virtual void notify_() const = 0; virtual QVariant get_variant() const noexcept = 0; }; diff --git a/options/bundle.hpp b/options/bundle.hpp index ce859bb1..158fcef9 100644 --- a/options/bundle.hpp +++ b/options/bundle.hpp @@ -10,6 +10,7 @@ #include "group.hpp" #include "connector.hpp" +#include "compat/qhash.hpp" #include <memory> #include <tuple> diff --git a/options/connector.cpp b/options/connector.cpp index 40c99a82..e86958f7 100644 --- a/options/connector.cpp +++ b/options/connector.cpp @@ -64,6 +64,8 @@ void connector::on_value_created(value_type val) 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 : it->second) @@ -72,6 +74,8 @@ void connector::notify_values(const QString& name) const void connector::notify_all_values() const { + QMutexLocker l(get_mtx()); + for (const auto& [k, v] : connected_values) for (value_type val : v) val->notify(); @@ -79,6 +83,8 @@ void connector::notify_all_values() const void connector::set_all_to_default_() { + QMutexLocker l(get_mtx()); + for (auto& pair : connected_values) for (auto& val : pair.second) val->set_to_default(); diff --git a/options/connector.hpp b/options/connector.hpp index ffef2a3d..025efda2 100644 --- a/options/connector.hpp +++ b/options/connector.hpp @@ -8,12 +8,13 @@ #pragma once +#include "compat/qhash.hpp" + #include <unordered_map> #include <vector> #include <QString> #include <QMutex> -#include <QHashFunctions> #include "export.hpp" diff --git a/options/defs.hpp b/options/defs.hpp index 9ea4f3b3..797a8fda 100644 --- a/options/defs.hpp +++ b/options/defs.hpp @@ -2,5 +2,3 @@ #define OPENTRACK_PROFILE_FILENAME_KEY "settings-filename" #define OPENTRACK_DEFAULT_PROFILE "default.ini" - - diff --git a/options/globals.cpp b/options/globals.cpp index 099a7208..39eb6014 100644 --- a/options/globals.cpp +++ b/options/globals.cpp @@ -1,10 +1,13 @@ #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 { @@ -124,9 +127,24 @@ QString ini_combine(const QString& filename) QStringList ini_list() { - QDir settings_dir(ini_directory()); + 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; - auto list = settings_dir.entryList({ QStringLiteral("*.ini") }, f::Files | f::Readable, QDir::Name); + list = settings_dir.entryList({ QStringLiteral("*.ini") }, f::Files | f::Readable, QDir::Name); std::sort(list.begin(), list.end()); return list; } @@ -154,8 +172,8 @@ fail: constexpr const char* subdir = "ini"; QString dir = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).value(0, QString()); if (dir.isEmpty()) goto fail; -#if !defined _WIN32 && !defined __APPLE__ 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()); diff --git a/options/globals.hpp b/options/globals.hpp index 7af6533d..af242dc9 100644 --- a/options/globals.hpp +++ b/options/globals.hpp @@ -1,7 +1,7 @@ #pragma once #include "export.hpp" -#include "compat/macros.hpp" +#include "compat/macros.h" #include <optional> diff --git a/options/group.hpp b/options/group.hpp index 751eee34..11bab965 100644 --- a/options/group.hpp +++ b/options/group.hpp @@ -4,7 +4,8 @@ #include "compat/base-path.hpp" #include "compat/library-path.hpp" -#include "compat/macros.hpp" +#include "compat/macros.h" +#include "compat/qhash.hpp" #include "export.hpp" #include <optional> 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 7962b81b..7430c00a 100644 --- a/options/metatype.cpp +++ b/options/metatype.cpp @@ -1,5 +1,4 @@ #include <QMetaType> -#include "compat/macros.hpp" namespace options::detail { diff --git a/options/scoped.cpp b/options/scoped.cpp index f5e219e2..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> @@ -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(); diff --git a/options/scoped.hpp b/options/scoped.hpp index dd7dbacf..81e6bd19 100644 --- a/options/scoped.hpp +++ b/options/scoped.hpp @@ -27,9 +27,13 @@ struct OTR_OPTIONS_EXPORT opts 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.hpp b/options/slider.hpp index 5d21bf0f..1e721ae0 100644 --- a/options/slider.hpp +++ b/options/slider.hpp @@ -8,7 +8,6 @@ #pragma once #include "export.hpp" -#include "compat/macros.hpp" #include <type_traits> diff --git a/options/tie.cpp b/options/tie.cpp index 43e6c596..adf26b53 100644 --- a/options/tie.cpp +++ b/options/tie.cpp @@ -8,7 +8,7 @@ #include "tie.hpp" #include "compat/run-in-thread.hpp" -#include "compat/macros.hpp" +#include "compat/macros.h" #include "value-traits.hpp" @@ -28,8 +28,12 @@ void tie_setting(value<QString>& v, QComboBox* cb) { cb->setCurrentText(v); v = cb->currentText(); - value_::connect(cb, SIGNAL(currentTextChanged(QString)), &v, SLOT(setValue(const QString&)), v.DIRECT_CONNTYPE); - 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); } void tie_setting(value<QVariant>& v, QComboBox* cb) @@ -39,13 +43,10 @@ void tie_setting(value<QVariant>& v, QComboBox* cb) 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; }; @@ -58,16 +59,11 @@ void tie_setting(value<QVariant>& v, QComboBox* cb) v = {}; value_::connect(cb, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), - &v, [cb, &v](int idx) { - v = cb->itemData(idx); - }, v.DIRECT_CONNTYPE); + &v, [cb, &v](int idx) { v = cb->itemData(idx); }, + v.DIRECT_CONNTYPE); value_::connect(&v, value_::value_changed<QVariant>(), - cb, - [cb, set_idx](const QVariant& var) { - run_in_thread_sync(cb, [&] { - set_idx(var); - }); - }, v.DIRECT_CONNTYPE); + cb, [set_idx](const QVariant& var) { set_idx(var); }, + v.SAFE_CONNTYPE); } void tie_setting(value<bool>& v, QRadioButton* cb) @@ -128,10 +124,7 @@ void tie_setting(value<slider_value>& v, QSlider* w) v = v().update_from_slider(w->value(), q_min, q_max); } - value_::connect(w, - &QSlider::valueChanged, - &v, - [=, &v](int pos) + value_::connect(w, &QSlider::valueChanged, &v, [=, &v](int pos) { run_in_thread_sync(w, [&]() { diff --git a/options/tie.hpp b/options/tie.hpp index 2ac27d64..194a3a5d 100644 --- a/options/tie.hpp +++ b/options/tie.hpp @@ -38,16 +38,12 @@ std::enable_if_t<std::is_enum_v<t>> tie_setting(value<t>& v, QComboBox* cb) v = static_cast<t>(cb->currentData().toInt()); 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); + &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) { - run_in_thread_sync(cb, [=] { cb->setCurrentIndex(cb->findData(x)); }); - }, v.DIRECT_CONNTYPE); + cb, [cb](int x) { cb->setCurrentIndex(cb->findData(x)); }, + v.SAFE_CONNTYPE); } template<typename t, typename From, typename To> @@ -57,23 +53,17 @@ void tie_setting(value<t>& v, QComboBox* cb, From&& fn_to_index, To&& fn_to_valu 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) { - run_in_thread_sync(cb, [&] { - v = fn_to_value(idx, cb->currentData()); - }); - }, v.DIRECT_CONNTYPE); + &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](cv_qualified<t>& v) { - run_in_thread_sync(cb, [&] { - cb->setCurrentIndex(fn_to_index(v)); - }); - }, v.DIRECT_CONNTYPE); + 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 = [lb, fun](cv_qualified<t> v) { lb->setText(fun(v)); }; + auto closure = [lb, fun](detail::cv_qualified<t> v) { lb->setText(fun(v)); }; closure(v()); value_::connect(&v, value_::value_changed<t>(), diff --git a/options/value-traits.hpp b/options/value-traits.hpp index aeb34cfa..145cd924 100644 --- a/options/value-traits.hpp +++ b/options/value-traits.hpp @@ -9,97 +9,110 @@ #include <QString> namespace options::detail { - -template<typename t, typename Enable = void> -struct value_traits; +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 value_type = t; using stored_type = u; - using self = value_traits<value_type>; - static value_type value_with_default(const value_type& val, const value_type&) + static inline + value_type value_with_default(cv_qualified<value_type> val, cv_qualified<value_type>) { return val; } - static value_type value_from_storage(const stored_type& x) + static inline + value_type value_from_storage(cv_qualified<stored_type> x) { return static_cast<value_type>(x); } - static stored_type storage_from_value(const value_type& val) + static inline + stored_type storage_from_value(cv_qualified<value_type> val) { return static_cast<stored_type>(val); } - static value_type value_from_qvariant(const QVariant& x) + static inline + value_type value_from_qvariant(const QVariant& x) { - return self::value_from_storage(self::storage_from_qvariant(x)); + return value_from_storage(storage_from_qvariant(x)); } - static QVariant qvariant_from_value(const value_type& val) + static inline + QVariant qvariant_from_value(cv_qualified<value_type> val) { - return self::qvariant_from_storage(self::storage_from_value(val)); + return qvariant_from_storage(storage_from_value(val)); } - static constexpr value_type pass_value(const value_type& x) + 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 self::value_from_storage(self::storage_from_value(x)); + return value_from_storage(storage_from_value(x)); } - static stored_type storage_from_qvariant(const QVariant& x) + static inline + stored_type storage_from_qvariant(const QVariant& x) { // XXX TODO return x.value<stored_type>(); } - static QVariant qvariant_from_storage(const stored_type& val) + static inline + QVariant qvariant_from_storage(cv_qualified<stored_type> val) { // XXX TODO return QVariant::fromValue<stored_type>(val); } - static bool is_equal(const value_type& x, const value_type& y) + static inline + bool is_equal(cv_qualified<value_type> x, cv_qualified<value_type> y) { return x == y; } }; -template<typename t, typename Enable> +template<typename t, typename Enable = void> struct value_traits : default_value_traits<t> {}; template<> -struct value_traits<double> : default_value_traits<double> +inline +bool default_value_traits<double>::is_equal(double x, double y) { - static bool is_equal(value_type x, value_type y) { return std::fabs(x - y) < 1e-6; } -}; + return std::fabs(x - y) < 1e-6; +} + +template<> struct value_traits<float, double> : default_value_traits<float> {}; template<> -struct value_traits<float> : default_value_traits<float> +inline +bool default_value_traits<float, double>::is_equal(float x, float y) { - static bool is_equal(value_type x, value_type y) { return std::fabs(x - y) < 1e-6f; } -}; + return std::fabs(x - y) < 1e-6f; +} template<> -struct value_traits<slider_value> : default_value_traits<slider_value> +inline +slider_value default_value_traits<slider_value>::value_with_default(cv_qualified<slider_value> val, cv_qualified<slider_value> def) { - static slider_value value_with_default(const slider_value& val, const slider_value& def) - { - return { val.cur(), def.min(), def.max() }; - } + return { val.cur(), def.min(), def.max() }; +} - static bool is_equal(const slider_value& x, const slider_value& y) - { - using tr = value_traits<double>; - return tr::is_equal(x.cur(), y.cur()); - } -}; +template<> +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> diff --git a/options/value.hpp b/options/value.hpp index 92e2878f..9a7487b8 100644 --- a/options/value.hpp +++ b/options/value.hpp @@ -14,7 +14,6 @@ #include "slider.hpp" #include "base-value.hpp" #include "value-traits.hpp" -#include "compat/macros.hpp" #include <type_traits> #include <utility> @@ -38,8 +37,10 @@ namespace options { template<typename t> class value final : public value_ { - static_assert(std::is_same_v<t, remove_cvref_t<t>>); + 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>; never_inline @@ -92,10 +93,42 @@ public: return traits::qvariant_from_value(def); } + 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 void notify() const override { - if (!is_null()) - emit valueChanged(traits::storage_from_value(get())); + 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 -"); + } + } } auto& operator=(t&& datum) noexcept @@ -104,6 +137,7 @@ public: return *this; store_variant(traits::qvariant_from_value(traits::pass_value(datum))); + maybe_trace("set-value"); return *this; } @@ -126,7 +160,7 @@ public: static constexpr Qt::ConnectionType DIRECT_CONNTYPE = Qt::DirectConnection; static constexpr Qt::ConnectionType SAFE_CONNTYPE = Qt::QueuedConnection; - value(bundle b, const QString& name, t def) noexcept : value_(b, name), def(std::move(def)) + value(bundle b, const QString& name, t def) noexcept : value_(b, name), def(std::move(def)), cached_value{get()} { } 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/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 53bf214f..ac3aa74a 100644 --- a/pose-widget/pose-widget.cpp +++ b/pose-widget/pose-widget.cpp @@ -13,12 +13,42 @@ #include <QtEvents> #include <QDebug> -#include <QImage> +#include <QQuaternion> +#include <QMatrix4x4> namespace pose_widget_impl { pose_widget::pose_widget(QWidget* parent) : QWidget(parent) { + 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(); + + p.begin(&back); + p.setPen(QPen(Qt::darkGreen, 3, Qt::SolidLine)); + p.drawRect(0, 0, back.width()-1, back.height()-1); + p.end(); +#endif + + //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(); + + //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(); + + mirror.setFocusPolicy(Qt::NoFocus); } void pose_widget::present(double yaw, double pitch, double roll, double x, double y, double z) @@ -29,44 +59,81 @@ void pose_widget::present(double yaw, double pitch, double roll, double x, doubl repaint(); } -void pose_widget::paintEvent(QPaintEvent*) +void pose_widget::resizeEvent(QResizeEvent *event) { - auto [ yaw, pitch, roll ] = R; - auto [ x, y, z ] = T; - - const QImage& img = (std::fabs(pitch) > 90) ^ (std::fabs(yaw) > 90) - ? back - : front; - - int w = img.width(), h = img.height(); - - QTransform t; - - t.translate(w*.5, h*.5); - - constexpr double z_scale = 1./250; - constexpr double xy_scale = .0075; - double xy = std::sqrt(w*w + h*h) * xy_scale; + // adapt to widget size + float w = event->size().width(); + float h = event->size().height(); - double s = clamp(.4 + -z * z_scale, .1, 2); - t.scale(s, s); - - t.rotate(pitch, Qt::XAxis); - t.rotate(yaw, Qt::YAxis); - t.rotate(roll, Qt::ZAxis); + // 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()); +} - t.translate(x * xy / s, y * xy / s); +void pose_widget::paintEvent(QPaintEvent*) +{ + // 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) - t.translate(w*-.5, h*-.5); + // get a local copy of input data + auto [ yaw, pitch, roll ] = R; + auto [ x, y, z ] = T; QPainter p(this); + #ifdef TEST + // use antialiasing for correct frame around the Octopus, only if TEST defined + p.setRenderHint(QPainter::Antialiasing, true); + #endif + + { + 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(); + } + + // 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(rect(), img); -} + p.drawImage(QPointF(0,0), forward ? front : back); -QSize pose_widget::sizeHint() const -{ - return { 1 << 16, 1 << 16 }; + // 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 b3267ff9..9152e960 100644 --- a/pose-widget/pose-widget.hpp +++ b/pose-widget/pose-widget.hpp @@ -14,7 +14,9 @@ #include <QWidget> #include <QImage> +#include <QCheckBox> +//#define TEST namespace pose_widget_impl { using namespace euler; @@ -22,16 +24,19 @@ using namespace euler; struct OTR_POSE_WIDGET_EXPORT pose_widget final : QWidget { public: - pose_widget(QWidget *parent = nullptr); + explicit pose_widget(QWidget *parent = nullptr); void present(double xAngle, double yAngle, double zAngle, double x, double y, double z); - QSize sizeHint() const override; - + QCheckBox mirror{"Mirror", this}; private: + 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)}; + 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}}; }; } 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/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'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/zh_CN.ts b/proto-flightgear/lang/zh_CN.ts index dc093c57..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> diff --git a/proto-fsuipc/lang/zh_CN.ts b/proto-fsuipc/lang/zh_CN.ts index 031e1916..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> diff --git a/proto-ft/ftnoir_ftcontrols.ui b/proto-ft/ftnoir_ftcontrols.ui index 0d142d09..8edf5a9d 100644 --- a/proto-ft/ftnoir_ftcontrols.ui +++ b/proto-ft/ftnoir_ftcontrols.ui @@ -12,22 +12,10 @@ <rect> <x>0</x> <y>0</y> - <width>508</width> - <height>232</height> + <width>533</width> + <height>326</height> </rect> </property> - <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="windowTitle"> <string>freetrack protocol settings</string> </property> @@ -42,6 +30,13 @@ <bool>false</bool> </property> <layout class="QGridLayout" name="gridLayout_2"> + <item row="5" column="0"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> <item row="0" column="0"> <widget class="QGroupBox" name="groupBox_3"> <property name="sizePolicy"> @@ -95,64 +90,138 @@ </layout> </widget> </item> - <item row="1" column="0"> + <item row="1" column="0" rowspan="2"> <widget class="QGroupBox" name="groupBox_4"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> <property name="title"> - <string>Repair NPClient location</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_2"> - <property name="spacing"> - <number>9</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="Minimum"> - <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. + <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> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <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> </widget> </item> - <item row="2" column="0"> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </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"/> diff --git a/proto-ft/ftnoir_protocol_ft.cpp b/proto-ft/ftnoir_protocol_ft.cpp index c6259593..7f710621 100644 --- a/proto-ft/ftnoir_protocol_ft.cpp +++ b/proto-ft/ftnoir_protocol_ft.cpp @@ -10,6 +10,7 @@ #include "ftnoir_protocol_ft.h" #include "csv/csv.h" +#include <QDir> #include <cstddef> #include <cmath> @@ -17,7 +18,23 @@ freetrack::~freetrack() { - dummyTrackIR.close(); + //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) == sizeof(std::int32_t)); @@ -133,7 +150,7 @@ void freetrack::start_dummy() { 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; @@ -141,15 +158,46 @@ 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() @@ -157,24 +205,8 @@ module_status freetrack::initialize() if (!shm.success()) return error(tr("Can't load freetrack memory mapping")); - bool use_ft = false, use_npclient = false; - - switch (s.intUsedInterface) { - case 0: - use_ft = true; - use_npclient = true; - break; - case 1: - use_ft = true; - break; - case 2: - use_npclient = true; - break; - default: - break; - } - - set_protocols(use_ft, use_npclient); + if (auto ret = set_protocols(); !ret.is_ok()) + return ret; pMemData->data.DataID = 1; pMemData->data.CamWidth = 100; @@ -195,7 +227,7 @@ module_status freetrack::initialize() 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 a59796dd..a07747d9 100644 --- a/proto-ft/ftnoir_protocol_ft.h +++ b/proto-ft/ftnoir_protocol_ft.h @@ -27,11 +27,12 @@ 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 : TR, public IProtocol @@ -58,7 +59,9 @@ private: void start_dummy(); 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 @@ -75,6 +78,7 @@ private slots: void selectDLL(); void doOK(); void doCancel(); + void set_custom_location(); }; class freetrackDll : public Metadata diff --git a/proto-ft/ftnoir_protocol_ft_dialog.cpp b/proto-ft/ftnoir_protocol_ft_dialog.cpp index 58077b87..90ff059a 100644 --- a/proto-ft/ftnoir_protocol_ft_dialog.cpp +++ b/proto-ft/ftnoir_protocol_ft_dialog.cpp @@ -22,7 +22,12 @@ FTControls::FTControls() connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel())); connect(ui.bntLocateNPClient, SIGNAL(clicked()), this, SLOT(selectDLL())); - tie_setting(s.intUsedInterface, ui.cbxSelectInterface); + 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); + + connect(ui.set_custom_location, &QAbstractButton::clicked, this, &FTControls::set_custom_location); } void FTControls::doOK() @@ -50,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 ec4f58da..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> @@ -52,6 +52,30 @@ Starting tracking will again overwrite the DLL locations.</source> <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're only evaluating opentrack, and haven'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> @@ -63,6 +87,14 @@ Starting tracking will again overwrite the DLL locations.</source> <source>Can't load freetrack memory mapping</source> <translation type="unfinished"></translation> </message> + <message> + <source>Can't copy library to selected custom location '%1'</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>proto/freetrack: wrong interface selection '%1'</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>freetrackDll</name> diff --git a/proto-ft/lang/ru_RU.ts b/proto-ft/lang/ru_RU.ts index c793ae6b..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'а </translation> - </message> - <message> <source>Locate DLL</source> <translation>Укажите DLL</translation> </message> @@ -54,6 +54,30 @@ Starting tracking will again overwrite the DLL locations.</source> <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're only evaluating opentrack, and haven'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> @@ -65,6 +89,14 @@ Starting tracking will again overwrite the DLL locations.</source> <source>Can't load freetrack memory mapping</source> <translation type="unfinished"></translation> </message> + <message> + <source>Can't copy library to selected custom location '%1'</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>proto/freetrack: wrong interface selection '%1'</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>freetrackDll</name> diff --git a/proto-ft/lang/stub.ts b/proto-ft/lang/stub.ts index 635844a5..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> @@ -52,6 +52,30 @@ Starting tracking will again overwrite the DLL locations.</source> <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're only evaluating opentrack, and haven'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> @@ -63,6 +87,14 @@ Starting tracking will again overwrite the DLL locations.</source> <source>Can't load freetrack memory mapping</source> <translation type="unfinished"></translation> </message> + <message> + <source>Can't copy library to selected custom location '%1'</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>proto/freetrack: wrong interface selection '%1'</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>freetrackDll</name> diff --git a/proto-ft/lang/zh_CN.ts b/proto-ft/lang/zh_CN.ts index 442a1aa1..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> @@ -52,6 +52,30 @@ Starting tracking will again overwrite the DLL locations.</source> <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're only evaluating opentrack, and haven'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> @@ -63,6 +87,14 @@ Starting tracking will again overwrite the DLL locations.</source> <source>Can't load freetrack memory mapping</source> <translation type="unfinished"></translation> </message> + <message> + <source>Can't copy library to selected custom location '%1'</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>proto/freetrack: wrong interface selection '%1'</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>freetrackDll</name> diff --git a/proto-iokit-foohid/CMakeLists.txt b/proto-iokit-foohid/CMakeLists.txt index 31d3dcdc..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) 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 6f163d8f..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); } @@ -49,12 +47,12 @@ static uint8_t valueToStick(FooHIDJoystick *stick, double min, double value, dou 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/lang/zh_CN.ts b/proto-iokit-foohid/lang/zh_CN.ts index 6401616d..e5ca8aa9 100644 --- a/proto-iokit-foohid/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 5b07beff..fefcd9bb 100644 --- a/proto-libevdev/ftnoir_protocol_libevdev.cpp +++ b/proto-libevdev/ftnoir_protocol_libevdev.cpp @@ -55,6 +55,11 @@ evdev::evdev() 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; @@ -120,7 +125,7 @@ void evdev::pose(const double* headpose, const double*) { for (int i = 0; i < 6; i++) { int value = (int)(headpose[i] * mid_input / max_value[i] + mid_input); - int normalized = clamp(value, min_input, max_input); + int normalized = std::clamp(value, min_input, max_input); (void) libevdev_uinput_write_event(uidev, EV_ABS, axes[i], normalized); } diff --git a/proto-libevdev/ftnoir_protocol_libevdev.h b/proto-libevdev/ftnoir_protocol_libevdev.h index 0b0f2612..b81c3baf 100644 --- a/proto-libevdev/ftnoir_protocol_libevdev.h +++ b/proto-libevdev/ftnoir_protocol_libevdev.h @@ -7,7 +7,6 @@ #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> 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 462ea6fc..d6a2c333 100644 --- a/proto-libevdev/lang/nl_NL.ts +++ b/proto-libevdev/lang/nl_NL.ts @@ -4,10 +4,6 @@ <context> <name>UICLibevdevControls</name> <message> - <source>VJoy</source> - <translation type="unfinished"></translation> - </message> - <message> <source>Make sure rw for /dev/input/uinput!</source> <translation type="unfinished"></translation> </message> @@ -19,6 +15,10 @@ <source>Cancel</source> <translation type="unfinished"></translation> </message> + <message> + <source>libevdev options</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>evdev</name> diff --git a/proto-libevdev/lang/ru_RU.ts b/proto-libevdev/lang/ru_RU.ts index f659b1dd..c6310cd0 100644 --- a/proto-libevdev/lang/ru_RU.ts +++ b/proto-libevdev/lang/ru_RU.ts @@ -4,10 +4,6 @@ <context> <name>UICLibevdevControls</name> <message> - <source>VJoy</source> - <translation type="unfinished"></translation> - </message> - <message> <source>Make sure rw for /dev/input/uinput!</source> <translation type="unfinished"></translation> </message> @@ -19,6 +15,10 @@ <source>Cancel</source> <translation type="unfinished"></translation> </message> + <message> + <source>libevdev options</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>evdev</name> diff --git a/proto-libevdev/lang/stub.ts b/proto-libevdev/lang/stub.ts index 5250e21a..ffafa2ec 100644 --- a/proto-libevdev/lang/stub.ts +++ b/proto-libevdev/lang/stub.ts @@ -4,10 +4,6 @@ <context> <name>UICLibevdevControls</name> <message> - <source>VJoy</source> - <translation type="unfinished"></translation> - </message> - <message> <source>Make sure rw for /dev/input/uinput!</source> <translation type="unfinished"></translation> </message> @@ -19,6 +15,10 @@ <source>Cancel</source> <translation type="unfinished"></translation> </message> + <message> + <source>libevdev options</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>evdev</name> diff --git a/proto-libevdev/lang/zh_CN.ts b/proto-libevdev/lang/zh_CN.ts index 5250e21a..f5e8694c 100644 --- a/proto-libevdev/lang/zh_CN.ts +++ b/proto-libevdev/lang/zh_CN.ts @@ -1,13 +1,9 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<TS version="2.1" language="zh_CN"> <context> <name>UICLibevdevControls</name> <message> - <source>VJoy</source> - <translation type="unfinished"></translation> - </message> - <message> <source>Make sure rw for /dev/input/uinput!</source> <translation type="unfinished"></translation> </message> @@ -19,6 +15,10 @@ <source>Cancel</source> <translation type="unfinished"></translation> </message> + <message> + <source>libevdev options</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>evdev</name> diff --git a/proto-mouse/ftnoir_protocol_mouse.cpp b/proto-mouse/ftnoir_protocol_mouse.cpp index aed49f2b..e5b2192b 100644 --- a/proto-mouse/ftnoir_protocol_mouse.cpp +++ b/proto-mouse/ftnoir_protocol_mouse.cpp @@ -31,12 +31,12 @@ void mouse::pose(const double* headpose, const double*) int mouse_x = 0, mouse_y = 0; - if (axis_x == clamp(axis_x, Axis_MIN, Axis_MAX)) + 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, axis_x >= 3); - if (axis_y == clamp(axis_y, Axis_MIN, Axis_MAX)) + 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, axis_y >= 3); diff --git a/proto-mouse/lang/zh_CN.ts b/proto-mouse/lang/zh_CN.ts index 2a811df1..e69c4bfe 100644 --- a/proto-mouse/lang/zh_CN.ts +++ b/proto-mouse/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_mouse</name> <message> @@ -9,47 +9,47 @@ </message> <message> <source>X axis</source> - <translation type="unfinished"></translation> + <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 type="unfinished"></translation> + <translation>滚转</translation> </message> <message> <source>Y axis</source> - <translation type="unfinished"></translation> + <translation>Y 轴</translation> </message> <message> <source>Sensitivity</source> - <translation type="unfinished"></translation> + <translation>灵敏度</translation> </message> <message> <source>Method</source> - <translation type="unfinished"></translation> + <translation>方式</translation> </message> <message> <source>Direct input</source> 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 Binary files differnew file mode 100644 index 00000000..3ef546e9 --- /dev/null +++ b/proto-osc/images/osc-icon.png diff --git a/filter-kalman/lang/nl_NL.ts b/proto-osc/lang/nl_NL.ts index e3c61bbb..260b7adc 100644 --- a/filter-kalman/lang/nl_NL.ts +++ b/proto-osc/lang/nl_NL.ts @@ -2,36 +2,43 @@ <!DOCTYPE TS> <TS version="2.1" language="nl_NL"> <context> - <name>KalmanUICdialog_kalman</name> + <name>OSC_Dialog</name> <message> - <source>Kalman settings</source> + <source>OSC protocol settings</source> <translation type="unfinished"></translation> </message> <message> - <source>Measurement noise</source> + <source>Port</source> <translation type="unfinished"></translation> </message> <message> - <source>Rotation</source> + <source>Destination address</source> <translation type="unfinished"></translation> </message> +</context> +<context> + <name>osc_metadata</name> <message> - <source>Position</source> + <source>Open Sound Control</source> <translation type="unfinished"></translation> </message> +</context> +<context> + <name>osc_proto</name> <message> - <source>°</source> + <source>Open Sound Control</source> <translation type="unfinished"></translation> </message> <message> - <source>-</source> + <source>Error binding socket to INADDR_ANY</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1: %2</source> <translation type="unfinished"></translation> </message> -</context> -<context> - <name>kalmanDll</name> <message> - <source>Kalman</source> + <source>Invalid destination address '%1'</source> <translation type="unfinished"></translation> </message> </context> diff --git a/filter-kalman/lang/ru_RU.ts b/proto-osc/lang/ru_RU.ts index 6fda97be..498d68d6 100644 --- a/filter-kalman/lang/ru_RU.ts +++ b/proto-osc/lang/ru_RU.ts @@ -2,36 +2,43 @@ <!DOCTYPE TS> <TS version="2.1" language="ru_RU"> <context> - <name>KalmanUICdialog_kalman</name> + <name>OSC_Dialog</name> <message> - <source>Kalman settings</source> + <source>OSC protocol settings</source> <translation type="unfinished"></translation> </message> <message> - <source>Measurement noise</source> + <source>Port</source> <translation type="unfinished"></translation> </message> <message> - <source>Rotation</source> + <source>Destination address</source> <translation type="unfinished"></translation> </message> +</context> +<context> + <name>osc_metadata</name> <message> - <source>Position</source> + <source>Open Sound Control</source> <translation type="unfinished"></translation> </message> +</context> +<context> + <name>osc_proto</name> <message> - <source>°</source> + <source>Open Sound Control</source> <translation type="unfinished"></translation> </message> <message> - <source>-</source> + <source>Error binding socket to INADDR_ANY</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1: %2</source> <translation type="unfinished"></translation> </message> -</context> -<context> - <name>kalmanDll</name> <message> - <source>Kalman</source> + <source>Invalid destination address '%1'</source> <translation type="unfinished"></translation> </message> </context> diff --git a/filter-kalman/lang/stub.ts b/proto-osc/lang/stub.ts index 29fae5c0..20828cbd 100644 --- a/filter-kalman/lang/stub.ts +++ b/proto-osc/lang/stub.ts @@ -2,36 +2,43 @@ <!DOCTYPE TS> <TS version="2.1"> <context> - <name>KalmanUICdialog_kalman</name> + <name>OSC_Dialog</name> <message> - <source>Kalman settings</source> + <source>OSC protocol settings</source> <translation type="unfinished"></translation> </message> <message> - <source>Measurement noise</source> + <source>Port</source> <translation type="unfinished"></translation> </message> <message> - <source>Rotation</source> + <source>Destination address</source> <translation type="unfinished"></translation> </message> +</context> +<context> + <name>osc_metadata</name> <message> - <source>Position</source> + <source>Open Sound Control</source> <translation type="unfinished"></translation> </message> +</context> +<context> + <name>osc_proto</name> <message> - <source>°</source> + <source>Open Sound Control</source> <translation type="unfinished"></translation> </message> <message> - <source>-</source> + <source>Error binding socket to INADDR_ANY</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1: %2</source> <translation type="unfinished"></translation> </message> -</context> -<context> - <name>kalmanDll</name> <message> - <source>Kalman</source> + <source>Invalid destination address '%1'</source> <translation type="unfinished"></translation> </message> </context> diff --git a/filter-kalman/lang/zh_CN.ts b/proto-osc/lang/zh_CN.ts index 29fae5c0..e0d49844 100644 --- a/filter-kalman/lang/zh_CN.ts +++ b/proto-osc/lang/zh_CN.ts @@ -1,37 +1,44 @@ <?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>OSC_Dialog</name> <message> - <source>Kalman settings</source> + <source>OSC protocol settings</source> <translation type="unfinished"></translation> </message> <message> - <source>Measurement noise</source> + <source>Port</source> <translation type="unfinished"></translation> </message> <message> - <source>Rotation</source> + <source>Destination address</source> <translation type="unfinished"></translation> </message> +</context> +<context> + <name>osc_metadata</name> <message> - <source>Position</source> + <source>Open Sound Control</source> <translation type="unfinished"></translation> </message> +</context> +<context> + <name>osc_proto</name> <message> - <source>°</source> + <source>Open Sound Control</source> <translation type="unfinished"></translation> </message> <message> - <source>-</source> + <source>Error binding socket to INADDR_ANY</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1: %2</source> <translation type="unfinished"></translation> </message> -</context> -<context> - <name>kalmanDll</name> <message> - <source>Kalman</source> + <source>Invalid destination address '%1'</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/ftnoir_protocol_sc.cpp b/proto-simconnect/ftnoir_protocol_sc.cpp index 226a427b..ca76e0ce 100644 --- a/proto-simconnect/ftnoir_protocol_sc.cpp +++ b/proto-simconnect/ftnoir_protocol_sc.cpp @@ -43,20 +43,15 @@ void simconnect::run() qDebug() << "fsx: connect failed, retry in" << sleep_time << "seconds..."; else { - Timer tm; - - if (!SUCCEEDED(hr = simconnect_subscribe(handle, 0, "Frame"))) + if (!SUCCEEDED(hr = simconnect_subscribe(handle, 0, "1sec"))) qDebug() << "fsx: can't subscribe to frame event:" << (void*)hr; else { while (!isInterruptionRequested()) { - constexpr int max_idle_seconds = 2; - - if (WaitForSingleObject(event, 0) == WAIT_OBJECT_0) - tm.start(); + constexpr int max_idle_ms = 2000; - if ((int)tm.elapsed_seconds() > max_idle_seconds) + if (WaitForSingleObject(event, max_idle_ms) != WAIT_OBJECT_0) { qDebug() << "fsx: timeout reached, reconnecting"; break; @@ -73,7 +68,9 @@ void simconnect::run() } } + QMutexLocker l(&mtx); (void)simconnect_close(handle); + handle = nullptr; } for (unsigned k = 0; k < sleep_time * 25; k++) @@ -91,8 +88,6 @@ void simconnect::run() void simconnect::pose(const double* pose, const double*) { - QMutexLocker l(&mtx); - data[Pitch] = (float)-pose[Pitch]; data[Yaw] = (float)pose[Yaw]; data[Roll] = (float)pose[Roll]; @@ -101,6 +96,12 @@ void simconnect::pose(const double* pose, const double*) 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]); } module_status simconnect::initialize() @@ -148,14 +149,6 @@ module_status simconnect::initialize() return {}; } -void simconnect::handler() -{ - QMutexLocker l(&mtx); - (void)simconnect_set6DOF(handle, - data[TX], data[TY], data[TZ], - data[Pitch], data[Roll], data[Yaw]); -} - void simconnect::event_handler(SIMCONNECT_RECV* pData, DWORD, void* self_) { simconnect& self = *reinterpret_cast<simconnect*>(self_); @@ -172,9 +165,6 @@ void simconnect::event_handler(SIMCONNECT_RECV* pData, DWORD, void* self_) qDebug() << "fsx: got quit event"; self.reconnect = true; break; - case SIMCONNECT_RECV_ID_EVENT_FRAME: - self.handler(); - break; } } diff --git a/proto-simconnect/ftnoir_protocol_sc.h b/proto-simconnect/ftnoir_protocol_sc.h index 28b9b1d8..df3a538d 100644 --- a/proto-simconnect/ftnoir_protocol_sc.h +++ b/proto-simconnect/ftnoir_protocol_sc.h @@ -47,8 +47,6 @@ public: void run() override; private: - void handler(); - enum { SIMCONNECT_RECV_ID_EXCEPTION = 2, SIMCONNECT_RECV_ID_QUIT = 3, diff --git a/proto-simconnect/lang/zh_CN.ts b/proto-simconnect/lang/zh_CN.ts index 50d0fa8a..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> diff --git a/proto-udp/ftnoir_protocol_ftn.cpp b/proto-udp/ftnoir_protocol_ftn.cpp index f04f4f4c..c67c39b8 100644 --- a/proto-udp/ftnoir_protocol_ftn.cpp +++ b/proto-udp/ftnoir_protocol_ftn.cpp @@ -16,7 +16,7 @@ udp::udp() { set_dest_address(); - QObject::connect(s.b.get(), &bundle_::changed, + QObject::connect(&*s.b, &bundle_::changed, this, &udp::set_dest_address, Qt::DirectConnection); } 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'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/zh_CN.ts b/proto-udp/lang/zh_CN.ts index ca1bc409..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> diff --git a/proto-vjoystick/CMakeLists.txt b/proto-vjoystick/CMakeLists.txt index 3dbf5bea..5b6a1f07 100644 --- a/proto-vjoystick/CMakeLists.txt +++ b/proto-vjoystick/CMakeLists.txt @@ -11,6 +11,6 @@ if(WIN32 AND opentrack-intel) 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 a5ec2128..056563f7 100644 --- a/proto-vjoystick/lang/nl_NL.ts +++ b/proto-vjoystick/lang/nl_NL.ts @@ -20,10 +20,6 @@ <translation type="unfinished"></translation> </message> <message> - <source>driver/SDK version mismatch</source> - <translation type="unfinished"></translation> - </message> - <message> <source>Device missing. Add joystick #1.</source> <translation type="unfinished"></translation> </message> @@ -40,10 +36,6 @@ <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p>Go to the <a href="http://vjoystick.sourceforge.net/site/"><span style=" text-decoration: underline; color:#0000ff;">vjoystick</span></a> project site or <a href="https://sourceforge.net/projects/vjoystick/files/latest/download"><span style=" text-decoration: underline; color:#0000ff;">download directly</span></a>.</p></body></html></source> - <translation type="unfinished"></translation> - </message> - <message> <source>BUG: handle leak.</source> <translation type="unfinished"></translation> </message> @@ -59,6 +51,14 @@ <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><html><head/><body><p>Go to the <a href="https://github.com/jshafer817/vJoy"><span style=" text-decoration: underline; color:#0000ff;">vJoy</span></a> project site or <a href="https://github.com/jshafer817/vJoy/releases/tag/v2.1.9.1"><span style=" text-decoration: underline; color:#0000ff;">download directly</span></a> for Windows 10 and 11.</p></body></html></source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>vjoystick_metadata</name> diff --git a/proto-vjoystick/lang/ru_RU.ts b/proto-vjoystick/lang/ru_RU.ts index 47c93a83..4c9aacd3 100644 --- a/proto-vjoystick/lang/ru_RU.ts +++ b/proto-vjoystick/lang/ru_RU.ts @@ -20,10 +20,6 @@ <translation type="unfinished"></translation> </message> <message> - <source>driver/SDK version mismatch</source> - <translation type="unfinished"></translation> - </message> - <message> <source>Device missing. Add joystick #1.</source> <translation type="unfinished"></translation> </message> @@ -40,10 +36,6 @@ <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p>Go to the <a href="http://vjoystick.sourceforge.net/site/"><span style=" text-decoration: underline; color:#0000ff;">vjoystick</span></a> project site or <a href="https://sourceforge.net/projects/vjoystick/files/latest/download"><span style=" text-decoration: underline; color:#0000ff;">download directly</span></a>.</p></body></html></source> - <translation type="unfinished"></translation> - </message> - <message> <source>BUG: handle leak.</source> <translation type="unfinished"></translation> </message> @@ -59,6 +51,14 @@ <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><html><head/><body><p>Go to the <a href="https://github.com/jshafer817/vJoy"><span style=" text-decoration: underline; color:#0000ff;">vJoy</span></a> project site or <a href="https://github.com/jshafer817/vJoy/releases/tag/v2.1.9.1"><span style=" text-decoration: underline; color:#0000ff;">download directly</span></a> for Windows 10 and 11.</p></body></html></source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>vjoystick_metadata</name> diff --git a/proto-vjoystick/lang/stub.ts b/proto-vjoystick/lang/stub.ts index 078888fa..8ec5c042 100644 --- a/proto-vjoystick/lang/stub.ts +++ b/proto-vjoystick/lang/stub.ts @@ -20,10 +20,6 @@ <translation type="unfinished"></translation> </message> <message> - <source>driver/SDK version mismatch</source> - <translation type="unfinished"></translation> - </message> - <message> <source>Device missing. Add joystick #1.</source> <translation type="unfinished"></translation> </message> @@ -40,10 +36,6 @@ <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p>Go to the <a href="http://vjoystick.sourceforge.net/site/"><span style=" text-decoration: underline; color:#0000ff;">vjoystick</span></a> project site or <a href="https://sourceforge.net/projects/vjoystick/files/latest/download"><span style=" text-decoration: underline; color:#0000ff;">download directly</span></a>.</p></body></html></source> - <translation type="unfinished"></translation> - </message> - <message> <source>BUG: handle leak.</source> <translation type="unfinished"></translation> </message> @@ -59,6 +51,14 @@ <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><html><head/><body><p>Go to the <a href="https://github.com/jshafer817/vJoy"><span style=" text-decoration: underline; color:#0000ff;">vJoy</span></a> project site or <a href="https://github.com/jshafer817/vJoy/releases/tag/v2.1.9.1"><span style=" text-decoration: underline; color:#0000ff;">download directly</span></a> for Windows 10 and 11.</p></body></html></source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>vjoystick_metadata</name> diff --git a/proto-vjoystick/lang/zh_CN.ts b/proto-vjoystick/lang/zh_CN.ts index 078888fa..95192e1d 100644 --- a/proto-vjoystick/lang/zh_CN.ts +++ b/proto-vjoystick/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>vjoystick</name> <message> @@ -20,10 +20,6 @@ <translation type="unfinished"></translation> </message> <message> - <source>driver/SDK version mismatch</source> - <translation type="unfinished"></translation> - </message> - <message> <source>Device missing. Add joystick #1.</source> <translation type="unfinished"></translation> </message> @@ -40,10 +36,6 @@ <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p>Go to the <a href="http://vjoystick.sourceforge.net/site/"><span style=" text-decoration: underline; color:#0000ff;">vjoystick</span></a> project site or <a href="https://sourceforge.net/projects/vjoystick/files/latest/download"><span style=" text-decoration: underline; color:#0000ff;">download directly</span></a>.</p></body></html></source> - <translation type="unfinished"></translation> - </message> - <message> <source>BUG: handle leak.</source> <translation type="unfinished"></translation> </message> @@ -59,6 +51,14 @@ <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><html><head/><body><p>Go to the <a href="https://github.com/jshafer817/vJoy"><span style=" text-decoration: underline; color:#0000ff;">vJoy</span></a> project site or <a href="https://github.com/jshafer817/vJoy/releases/tag/v2.1.9.1"><span style=" text-decoration: underline; color:#0000ff;">download directly</span></a> for Windows 10 and 11.</p></body></html></source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>vjoystick_metadata</name> diff --git a/proto-vjoystick/vjoystick.cpp b/proto-vjoystick/vjoystick.cpp index f322ced9..292e8259 100644 --- a/proto-vjoystick/vjoystick.cpp +++ b/proto-vjoystick/vjoystick.cpp @@ -88,7 +88,7 @@ int vjoystick::to_axis_value(unsigned axis_id, double val) const const double min = axis_min[axis_id]; const double max = axis_max[axis_id]; - return (int)(clamp((val+minmax) * max / (2*minmax) - min, min, max)); + return (int)(std::clamp((val+minmax) * max / (2*minmax) - min, min, max)); } vjoystick::vjoystick() = default; @@ -104,8 +104,11 @@ module_status vjoystick::initialize() 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"); + 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; diff --git a/proto-vjoystick/vjoystick.h b/proto-vjoystick/vjoystick.h index 39bc14e3..82ebd3e6 100644 --- a/proto-vjoystick/vjoystick.h +++ b/proto-vjoystick/vjoystick.h @@ -8,7 +8,6 @@ #pragma once #include "ui_vjoystick.h" #include "api/plugin-api.hpp" -#include "compat/macros.hpp" enum status { diff --git a/proto-vjoystick/vjoystick.ui b/proto-vjoystick/vjoystick.ui index 5bfdb02a..8092898b 100644 --- a/proto-vjoystick/vjoystick.ui +++ b/proto-vjoystick/vjoystick.ui @@ -21,7 +21,7 @@ <item row="0" column="0"> <widget class="QLabel" name="label"> <property name="text"> - <string><html><head/><body><p>Go to the <a href="http://vjoystick.sourceforge.net/site/"><span style=" text-decoration: underline; color:#0000ff;">vjoystick</span></a> project site or <a href="https://sourceforge.net/projects/vjoystick/files/latest/download"><span style=" text-decoration: underline; color:#0000ff;">download directly</span></a>.</p></body></html></string> + <string><html><head/><body><p>Go to the <a href="https://github.com/jshafer817/vJoy"><span style=" text-decoration: underline; color:#0000ff;">vJoy</span></a> project site or <a href="https://github.com/jshafer817/vJoy/releases/tag/v2.1.9.1"><span style=" text-decoration: underline; color:#0000ff;">download directly</span></a> for Windows 10 and 11.</p></body></html></string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> diff --git a/proto-wine/CMakeLists.txt b/proto-wine/CMakeLists.txt index b0f18bb2..ff4932cc 100644 --- a/proto-wine/CMakeLists.txt +++ b/proto-wine/CMakeLists.txt @@ -16,18 +16,22 @@ if(NOT WIN32) 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 wineg++ -mconsole -g -DNOMINMAX -O2 -m32 -std=c++17 -fPIC -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} -Wall -Wextra -Wpedantic ${my-rt}) 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 bed1fed3..bae02b06 100644 --- a/proto-wine/ftnoir_protocol_wine.cpp +++ b/proto-wine/ftnoir_protocol_wine.cpp @@ -1,11 +1,16 @@ #include "ftnoir_protocol_wine.h" -#include <QString> -#include <string.h> -#include <math.h> +#include <qprocess.h> #ifndef OTR_WINE_NO_WRAPPER # include "csv/csv.h" #endif -#include "compat/library-path.hpp" + +#include <cstring> +#include <cmath> + +#include <QString> +#include <QDebug> + +#include "proton.h" wine::wine() = default; @@ -16,11 +21,14 @@ wine::~wine() if (shm) { shm->stop = true; exit = wrapper.waitForFinished(100); + if (exit) + qDebug() << "proto/wine: wrapper exit code" << wrapper.exitCode(); } if (!exit) { - wrapper.kill(); - wrapper.waitForFinished(-1); + if (wrapper.state() != QProcess::NotRunning) + wrapper.kill(); + wrapper.waitForFinished(1000); } #endif //shm_unlink("/" WINE_SHM_NAME); @@ -38,6 +46,7 @@ void wine::pose(const double *headpose, const double*) #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 */ @@ -56,48 +65,136 @@ 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"; - auto env = QProcessEnvironment::systemEnvironment(); + 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)")); - QProcessEnvironment make_steam_environ(const QString& proton_version, int appid); - QString proton_path(const QString& proton_version); + auto [prefix, error_string, success] = make_wineprefix(s.proton_appid); + qDebug() << "proto/wine: wineprefix:" << prefix; + env.insert("WINEPREFIX", prefix); - wine_path = proton_path(s.proton_version); - env = make_steam_environ(s.proton_version, s.proton_appid); + if (!success) + return error(error_string); } - else - { - QString wineprefix = "~/.wine"; - if (!s.wineprefix->isEmpty()) + 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] != '/') - error(tr("Wine prefix must be an absolute path (given '%1')").arg(wineprefix)); + 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()) diff --git a/proto-wine/ftnoir_protocol_wine.h b/proto-wine/ftnoir_protocol_wine.h index 8175be03..718699ac 100644 --- a/proto-wine/ftnoir_protocol_wine.h +++ b/proto-wine/ftnoir_protocol_wine.h @@ -9,23 +9,30 @@ #include "options/options.hpp" using namespace options; -#include <QString> -#include <QProcess> #include <QMutex> +#include <QProcess> +#include <QString> +#include <QVariant> #include <QDebug> struct settings : opts { settings() : opts{"proto-wine"} {} - value<bool> variant_proton{b, "variant-proton", false }, - variant_wine{b, "variant-wine", true }, + 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<QString> proton_version{b, "proton-version", {} }, - wineprefix{b, "wineprefix", "~/.wine"}; + 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 @@ -75,6 +82,14 @@ private: settings s; private slots: + void onWinePathComboUpdated(); + void onRadioButtonsChanged(); + + void doBrowseWine(); + void doBrowseWinePrefix(); + + void doBrowseProtonPrefix(); + void doOK(); void doCancel(); }; diff --git a/proto-wine/ftnoir_protocol_wine_dialog.cpp b/proto-wine/ftnoir_protocol_wine_dialog.cpp index f1cb01c2..2b7d08e0 100644 --- a/proto-wine/ftnoir_protocol_wine_dialog.cpp +++ b/proto-wine/ftnoir_protocol_wine_dialog.cpp @@ -1,28 +1,222 @@ #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_versions[] = { - "4.11", "4.2", "3.16", "3.7", +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); - for (const char* version : proton_versions) - ui.proton_version->addItem(version, QVariant{version}); + // 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); - tie_setting(s.proton_version, ui.proton_version); - tie_setting(s.variant_wine, ui.variant_wine); - tie_setting(s.variant_proton, ui.variant_proton); + 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.proton_appid, ui.proton_appid); - tie_setting(s.wineprefix, ui.wineprefix); + 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::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::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() @@ -36,4 +230,3 @@ void FTControls::doCancel() s.b->reload(); close(); } - diff --git a/proto-wine/ftnoir_winecontrols.ui b/proto-wine/ftnoir_winecontrols.ui index a0a1e071..b601d6e5 100644 --- a/proto-wine/ftnoir_winecontrols.ui +++ b/proto-wine/ftnoir_winecontrols.ui @@ -9,8 +9,8 @@ <rect> <x>0</x> <y>0</y> - <width>544</width> - <height>410</height> + <width>934</width> + <height>466</height> </rect> </property> <property name="windowTitle"> @@ -32,8 +32,93 @@ <property name="title"> <string>Wine variant</string> </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="2" column="1" alignment="Qt::AlignRight"> + <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><html><head/><body><p>wine/runner exectuable path</p></body></html></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><html><head/><body><p>prefix</p></body></html></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"> @@ -47,10 +132,13 @@ <height>0</height> </size> </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> </widget> </item> - <item row="0" column="0"> - <widget class="QRadioButton" name="variant_wine"> + <item row="3" column="0"> + <widget class="QRadioButton" name="variant_proton"> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> <horstretch>0</horstretch> @@ -58,12 +146,12 @@ </sizepolicy> </property> <property name="text"> - <string>Wine (system)</string> + <string>Proton (select version and mode)</string> </property> </widget> </item> - <item row="2" column="0"> - <widget class="QRadioButton" name="variant_proton"> + <item row="0" column="0"> + <widget class="QRadioButton" name="variant_wine"> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> <horstretch>0</horstretch> @@ -71,24 +159,96 @@ </sizepolicy> </property> <property name="text"> - <string>Proton (Steam Play)</string> + <string>Wine (select version and prefix)</string> </property> </widget> </item> <item row="0" column="1"> - <widget class="QLineEdit" name="wineprefix"> + <widget class="QComboBox" name="wine_path_combo"> <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="minimumSize"> - <size> - <width>286</width> - <height>0</height> - </size> - </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><html><head/><body><p>Steam Play is Steams System to run Windows titles on Linux via Proton.</p></body></html></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><html><head/><body><p>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.</p></body></html></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> @@ -100,6 +260,9 @@ <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"> @@ -133,43 +296,79 @@ </widget> </item> <item> - <widget class="QWidget" name="widget" native="true"> + <widget class="QWidget" name="widget_2" native="true"> <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <layout class="QHBoxLayout" name="horizontalLayout"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> <item> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="label_2"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="text"> - <string>Steam application id</string> + <string>Protocol</string> </property> </widget> </item> <item> - <widget class="QSpinBox" name="proton_appid"> + <widget class="QComboBox" name="protocol_selection"> <property name="sizePolicy"> - <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="maximum"> - <number>2147483647</number> - </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="bottomMargin"> + <number>0</number> + </property> + </layout> + </widget> + </item> </layout> </widget> </item> @@ -188,6 +387,24 @@ </item> </layout> </widget> + <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> 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><html><head/><body><p>prefix</p></body></html></source> + <translation><html><head/><body><p>Prefix</p></body></html></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><html><head/><body><p>wine/runner exectuable path</p></body></html></source> + <translation><html><head/><body><p>Pfad zum ausführbaren wine/runner</p></body></html></translation> + </message> + <message> + <source>Browse Wine Path</source> + <translation>Wine-Pfad suchen</translation> + </message> + <message> + <source>Advanced</source> + <translation>Erweitert</translation> + </message> + <message> + <source><html><head/><body><p>When supported.</p></body></html></source> + <translation><html><head/><body><p>Falls unterstützt.</p></body></html></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><html><head/><body><p>Steam Play is Steams System to run Windows titles on Linux via Proton.</p></body></html></source> + <translation><html><head/><body><p>Steam Play ist Steams System, um Windows Titel auf Linux, via Proton, auszuführen.</p></body></html></translation> + </message> + <message> + <source><html><head/><body><p>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.</p></body></html></source> + <translation><html><head/><body><p>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.</p></body></html></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 '%1')</source> + <translation>Wine-Prefix muss ein absoluter Pfad sein ('%1' 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'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 f71d8ad7..bc3ef604 100644 --- a/proto-wine/lang/nl_NL.ts +++ b/proto-wine/lang/nl_NL.ts @@ -2,17 +2,28 @@ <!DOCTYPE TS> <TS version="2.1" language="nl_NL"> <context> - <name>UICFTControls</name> + <name>FTControls</name> <message> - <source>Wine settings</source> + <source>Select path to Wine Binary</source> <translation type="unfinished"></translation> </message> <message> - <source>Wine variant</source> + <source>Select Wine Prefix</source> <translation type="unfinished"></translation> </message> <message> - <source>Wine (system)</source> + <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> @@ -32,11 +43,63 @@ <translation type="unfinished"></translation> </message> <message> - <source>Steam application id</source> + <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>Proton (Steam Play)</source> + <source><html><head/><body><p>prefix</p></body></html></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><html><head/><body><p>wine/runner exectuable path</p></body></html></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><html><head/><body><p>Steam Play is Steams System to run Windows titles on Linux via Proton.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p>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.</p></body></html></source> <translation type="unfinished"></translation> </message> </context> @@ -54,6 +117,14 @@ <source>Wine prefix must be an absolute path (given '%1')</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> diff --git a/proto-wine/lang/ru_RU.ts b/proto-wine/lang/ru_RU.ts index 9937083f..a7702454 100644 --- a/proto-wine/lang/ru_RU.ts +++ b/proto-wine/lang/ru_RU.ts @@ -2,17 +2,28 @@ <!DOCTYPE TS> <TS version="2.1" language="ru_RU"> <context> - <name>UICFTControls</name> + <name>FTControls</name> <message> - <source>Wine settings</source> + <source>Select path to Wine Binary</source> <translation type="unfinished"></translation> </message> <message> - <source>Wine variant</source> + <source>Select Wine Prefix</source> <translation type="unfinished"></translation> </message> <message> - <source>Wine (system)</source> + <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> @@ -32,11 +43,63 @@ <translation type="unfinished"></translation> </message> <message> - <source>Steam application id</source> + <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>Proton (Steam Play)</source> + <source><html><head/><body><p>prefix</p></body></html></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><html><head/><body><p>wine/runner exectuable path</p></body></html></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><html><head/><body><p>Steam Play is Steams System to run Windows titles on Linux via Proton.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p>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.</p></body></html></source> <translation type="unfinished"></translation> </message> </context> @@ -54,6 +117,14 @@ <source>Wine prefix must be an absolute path (given '%1')</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> diff --git a/proto-wine/lang/stub.ts b/proto-wine/lang/stub.ts index 9b74a44d..2c708749 100644 --- a/proto-wine/lang/stub.ts +++ b/proto-wine/lang/stub.ts @@ -2,17 +2,28 @@ <!DOCTYPE TS> <TS version="2.1"> <context> - <name>UICFTControls</name> + <name>FTControls</name> <message> - <source>Wine settings</source> + <source>Select path to Wine Binary</source> <translation type="unfinished"></translation> </message> <message> - <source>Wine variant</source> + <source>Select Wine Prefix</source> <translation type="unfinished"></translation> </message> <message> - <source>Wine (system)</source> + <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> @@ -32,11 +43,63 @@ <translation type="unfinished"></translation> </message> <message> - <source>Steam application id</source> + <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>Proton (Steam Play)</source> + <source><html><head/><body><p>prefix</p></body></html></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><html><head/><body><p>wine/runner exectuable path</p></body></html></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><html><head/><body><p>Steam Play is Steams System to run Windows titles on Linux via Proton.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p>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.</p></body></html></source> <translation type="unfinished"></translation> </message> </context> @@ -54,6 +117,14 @@ <source>Wine prefix must be an absolute path (given '%1')</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> diff --git a/proto-wine/lang/zh_CN.ts b/proto-wine/lang/zh_CN.ts index 9b74a44d..6884f266 100644 --- a/proto-wine/lang/zh_CN.ts +++ b/proto-wine/lang/zh_CN.ts @@ -1,18 +1,29 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<TS version="2.1" language="zh_CN"> <context> - <name>UICFTControls</name> + <name>FTControls</name> <message> - <source>Wine settings</source> + <source>Select path to Wine Binary</source> <translation type="unfinished"></translation> </message> <message> - <source>Wine variant</source> + <source>Select Wine Prefix</source> <translation type="unfinished"></translation> </message> <message> - <source>Wine (system)</source> + <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> @@ -32,11 +43,63 @@ <translation type="unfinished"></translation> </message> <message> - <source>Steam application id</source> + <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>Proton (Steam Play)</source> + <source><html><head/><body><p>prefix</p></body></html></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><html><head/><body><p>wine/runner exectuable path</p></body></html></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><html><head/><body><p>Steam Play is Steams System to run Windows titles on Linux via Proton.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p>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.</p></body></html></source> <translation type="unfinished"></translation> </message> </context> @@ -54,6 +117,14 @@ <source>Wine prefix must be an absolute path (given '%1')</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> diff --git a/proto-wine/opentrack-wrapper-wine-main.cxx b/proto-wine/opentrack-wrapper-wine-main.cxx index 3df3abc7..9248a212 100644 --- a/proto-wine/opentrack-wrapper-wine-main.cxx +++ b/proto-wine/opentrack-wrapper-wine-main.cxx @@ -5,7 +5,6 @@ #include <cstdio> #include "freetrackclient/fttypes.h" -#include "wine-shm.h" #include "compat/export.hpp" enum Axis { @@ -14,6 +13,7 @@ enum Axis { #define __WINE_OLE2_H #include "compat/shm.h" +#include "wine-shm.h" void create_registry_key(void); @@ -42,7 +42,6 @@ private: void* mem; void *hMutex, *hMapFile; }; -#include <windows.h> int main(void) { diff --git a/proto-wine/opentrack-wrapper-wine-windows.cxx b/proto-wine/opentrack-wrapper-wine-windows.cxx index 88ced502..c1d552e1 100644 --- a/proto-wine/opentrack-wrapper-wine-windows.cxx +++ b/proto-wine/opentrack-wrapper-wine-windows.cxx @@ -4,18 +4,22 @@ #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" #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 index 15306d26..998da748 100644 --- a/proto-wine/proton.cpp +++ b/proto-wine/proton.cpp @@ -7,56 +7,91 @@ #ifndef OTR_WINE_NO_WRAPPER +#include <QDebug> +#include <QDir> +#include <QFileInfo> #include <QtGlobal> -#include <QString> -#include <QProcessEnvironment> -QProcessEnvironment make_steam_environ(const QString& proton_version, int appid) +#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) { - auto ret = QProcessEnvironment::systemEnvironment(); + 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", proton_version); - return 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( - ":HOME/.local/share/Steam/steamapps/common/Proton PROTON/dist/bin" - ":HOME/.local/share/Steam/ubuntu12_32/steam-runtime/amd64/bin" - ":HOME/.local/share/Steam/ubuntu12_32/steam-runtime/amd64/usr/bin" + ":PROTON_DIST_PATH/bin" ); path += ':'; path += qgetenv("PATH"); - ret.insert("PATH", path); + env.insert("PATH", path); QString library_path = expand( - ":HOME/.local/share/Steam/steamapps/common/Proton PROTON/dist/lib" - ":HOME/.local/share/Steam/steamapps/common/Proton PROTON/dist/lib64" - ":HOME/.local/share/Steam/ubuntu12_32/steam-runtime/pinned_libs_32" - ":HOME/.local/share/Steam/ubuntu12_32/steam-runtime/pinned_libs_64" - ":HOME/.local/share/Steam/ubuntu12_32/steam-runtime/i386/lib/i386-linux-gnu" - ":HOME/.local/share/Steam/ubuntu12_32/steam-runtime/i386/lib" - ":HOME/.local/share/Steam/ubuntu12_32/steam-runtime/i386/usr/lib/i386-linux-gnu" - ":HOME/.local/share/Steam/ubuntu12_32/steam-runtime/i386/usr/lib" - ":HOME/.local/share/Steam/ubuntu12_32/steam-runtime/amd64/lib/x86_64-linux-gnu" - ":HOME/.local/share/Steam/ubuntu12_32/steam-runtime/amd64/lib" - ":HOME/.local/share/Steam/ubuntu12_32/steam-runtime/amd64/usr/lib/x86_64-linux-gnu" - ":HOME/.local/share/Steam/ubuntu12_32/steam-runtime/amd64/usr/lib" + ":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"); - ret.insert("LD_LIBRARY_PATH", library_path); - ret.insert("WINEPREFIX", expand("HOME/.local/share/Steam/steamapps/compatdata/%1/pfx").arg(appid)); + env.insert("LD_LIBRARY_PATH", library_path); - return ret; + return ret(env, error, error.isEmpty()); } -QString proton_path(const QString& proton_version) + +std::tuple<QString, QString, bool> make_wineprefix(int appid) { - QString wine_path = "HOME/.local/share/Steam/steamapps/common/Proton PROTON/dist/bin/wine"; - wine_path.replace("HOME", qgetenv("HOME")); - wine_path.replace("PROTON", proton_version); - return wine_path; + 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 536f647f..62e8bbec 100644 --- a/proto-wine/wine-shm.h +++ b/proto-wine/wine-shm.h @@ -8,17 +8,10 @@ # pragma clang diagnostic ignored "-Wreserved-id-macro" #endif -// OSX sdk 10.8 build error otherwise -#undef _LIBCPP_MSVCRT - #ifdef __clang__ # pragma clang diagnostic pop #endif -#include <memory> - -template<typename t> using ptr = std::shared_ptr<t>; - struct WineSHM { double data[6]; int gameid, gameid2; diff --git a/qxt-mini/CMakeLists.txt b/qxt-mini/CMakeLists.txt index 21b2b160..1b2496f6 100644 --- a/qxt-mini/CMakeLists.txt +++ b/qxt-mini/CMakeLists.txt @@ -3,7 +3,9 @@ if(UNIX OR APPLE) include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) otr_module(qxt-mini NO-COMPAT BIN) if(APPLE) - target_link_options(${self} PUBLIC -framework Carbon -framework CoreFoundation) + find_library(CoreFoundation CoreFoundation) + find_library(Carbon Carbon) + target_link_options(${self} PUBLIC -framework Carbon -I ${CoreFoundation}) else() otr_pkgconfig(${self} x11 xcb xproto) 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 index 6401616d..e5ca8aa9 100644 --- a/qxt-mini/lang/zh_CN.ts +++ b/qxt-mini/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/qxt-mini/powerset.hpp b/qxt-mini/powerset.hpp index d8a8ec9b..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> diff --git a/qxt-mini/qxtglobalshortcut.cpp b/qxt-mini/qxtglobalshortcut.cpp index 69514f88..dec11dc4 100644 --- a/qxt-mini/qxtglobalshortcut.cpp +++ b/qxt-mini/qxtglobalshortcut.cpp @@ -36,6 +36,10 @@ #include <QtDebug> #include <QtGlobal> +#ifdef __GNUG__ +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + QHash<QPair<quint32, quint32>, QxtGlobalShortcut*> QxtGlobalShortcutPrivate::shortcuts; void QxtGlobalShortcutPrivate::event_filter_installer::ensure_event_filter() @@ -55,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() : @@ -176,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 } 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_x11.cpp b/qxt-mini/qxtglobalshortcut_x11.cpp index 6647d803..01894cfc 100644 --- a/qxt-mini/qxtglobalshortcut_x11.cpp +++ b/qxt-mini/qxtglobalshortcut_x11.cpp @@ -355,9 +355,7 @@ 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"; } } 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 index b72a7a81..fb6e64d9 100644 --- a/sdk-paths-sthalik@Clang-Linux.cmake +++ b/sdk-paths-sthalik@Clang-Linux.cmake @@ -13,7 +13,8 @@ add_compile_options( -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") @@ -33,8 +34,14 @@ 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 "-fuse-ld=lld ${CMAKE_${x}_LINKER_FLAGS}") + set(CMAKE_${x}_LINKER_FLAGS "${CMAKE_${x}_LINKER_FLAGS}") endforeach() function(set_sdk var path) @@ -46,4 +53,3 @@ 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 index 895d68b2..97ad67ed 100644 --- a/sdk-paths-sthalik@Clang-windows.cmake +++ b/sdk-paths-sthalik@Clang-windows.cmake @@ -18,8 +18,6 @@ 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(EIGEN3_INCLUDE_DIR "eigen") - setq(SDK_FSUIPC "fsuipc") setq(SDK_HYDRA "SixenseSDK") diff --git a/sdk-paths-sthalik@GNU-windows.cmake b/sdk-paths-sthalik@GNU-windows.cmake index b9617719..593f537f 100644 --- a/sdk-paths-sthalik@GNU-windows.cmake +++ b/sdk-paths-sthalik@GNU-windows.cmake @@ -18,8 +18,6 @@ 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(EIGEN3_INCLUDE_DIR "eigen") - setq(SDK_FSUIPC "fsuipc") setq(SDK_HYDRA "SixenseSDK") diff --git a/sdk-paths-sthalik@MSVC-windows.cmake b/sdk-paths-sthalik@MSVC-windows.cmake index 7d87287a..187a5da8 100644 --- a/sdk-paths-sthalik@MSVC-windows.cmake +++ b/sdk-paths-sthalik@MSVC-windows.cmake @@ -2,11 +2,11 @@ # 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 \ +# "../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 +# -make-tool jom -platform win32-msvc -static-runtime # remember to change -MD to -MT in mkspecs/ # also add CFLAGS -Zi and LFLAGS -DEBUG @@ -18,23 +18,41 @@ function(setq name value) endfunction() set(opentrack_install-debug-info TRUE CACHE INTERNAL "" FORCE) -set(opentrack_maintainer-mode TRUE CACHE INTERNAL "" FORCE) -setq(Qt5_DIR "../qt-5.12.0/lib/cmake/Qt5") - -setq(EIGEN3_INCLUDE_DIR "eigen") -setq(OpenCV_DIR "opencv/build") -setq(SDL2_DIR "SDL2-win32") -setq(SDK_ARUCO_LIBPATH "aruco/build/src/aruco.lib") +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_KINECT20 "Kinect-v2.0") +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(SDK_PS3EYEDRIVER "PS3EYEDriver") -setq(SDK_REALSENSE "RSSDK-R2") -setq(SDK_RIFT_140 "LibOVR-140/build") -setq(SDK_VALVE_STEAMVR "steamvr") -setq(SDK_VJOYSTICK "vjoystick") +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) diff --git a/settings/facetracknoir supported games.csv b/settings/facetracknoir supported games.csv index d5c3ca2d..62e3ca23 100644 --- a/settings/facetracknoir supported games.csv +++ b/settings/facetracknoir supported games.csv @@ -697,3 +697,22 @@ No;Game Name;Game protocol;Supported since;Verified;By;INTERNATIONAL_ID;FTN_ID 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/axis-opts.cpp b/spline/axis-opts.cpp index 489008e6..a2b4941d 100644 --- a/spline/axis-opts.cpp +++ b/spline/axis-opts.cpp @@ -31,7 +31,8 @@ axis_opts::axis_opts(QString pfx, Axis idx) : 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)) diff --git a/spline/axis-opts.hpp b/spline/axis-opts.hpp index c773dd61..437a5faa 100644 --- a/spline/axis-opts.hpp +++ b/spline/axis-opts.hpp @@ -29,7 +29,11 @@ public: r15 = 15, r10 = 10, + t600 = 600, + t300 = 300, + t150 = 150, t100 = 100, + t75 = 75, t30 = 30, t20 = 20, t15 = 15, @@ -38,6 +42,10 @@ public: o_r180 = -180, o_r90 = -90, o_t75 = -75, + o_t100 = -100, + o_t150 = -150, + o_t300 = -300, + o_t600 = -600, x1000 = 1000, }; @@ -47,7 +55,7 @@ public: 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>()); } 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 2429ffc3..46e2095c 100644 --- a/spline/spline-widget.cpp +++ b/spline/spline-widget.cpp @@ -1,17 +1,22 @@ #include "spline-widget.hpp" #include "compat/math.hpp" -#include "compat/macros.hpp" #include <algorithm> #include <QPainter> +#include <QPainterPath> #include <QPixmap> #include <QString> #include <QToolTip> #include <QtEvents> +#include <QPainterPath> #include <QDebug> +#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) @@ -43,7 +48,7 @@ void spline_widget::set_config(base_spline* spl) if (spl) { std::shared_ptr<base_settings> s = spl->get_settings(); - connection = connect(s.get(), &base_settings::recomputed, + connection = connect(&*s, &base_settings::recomputed, this, [this] { reload_spline(); }, Qt::QueuedConnection); } @@ -78,10 +83,6 @@ bool spline_widget::is_preview_only() const return preview_only; } -#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) -# define OTR_OBSOLETE_QT_WORKAROUND -#endif - void spline_widget::drawBackground() { QPainter painter(&background_img); @@ -111,12 +112,12 @@ void spline_widget::drawBackground() 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.averageCharWidth(); + double space_width = metrics.boundingRect(' ').right(); #endif + painter.setPen(palette().text().color()); // vertical grid for (int i = 0; i <= maxy; i += ystep) @@ -152,11 +153,10 @@ void spline_widget::drawBackground() #ifndef OTR_OBSOLETE_QT_WORKAROUND double advance = metrics.horizontalAdvance(text); #else - double advance = rect.right(); + double advance = metrics.boundingRect(text).right(); #endif - painter.drawText(QPointF(x - advance/2 - rect.left(), - pixel_bounds.height() - rect.top() + rect.height()), + pixel_bounds.bottom() + metrics.lineSpacing()), text); } } @@ -631,8 +631,8 @@ QPointF spline_widget::pixel_to_point(const QPointF& point) if (snap_y > 0) y = snap(y, snap_y); - x = clamp(x, 0, config->max_input()); - y = clamp(y, 0, config->max_output()); + x = std::clamp(x, 0., config->max_input()); + y = std::clamp(y, 0., config->max_output()); return { x, y }; } diff --git a/spline/spline.cpp b/spline/spline.cpp index 21044b34..466a9a7f 100644 --- a/spline/spline.cpp +++ b/spline/spline.cpp @@ -42,8 +42,10 @@ void spline::set_tracking_active(bool value) const std::shared_ptr<settings> S; { QMutexLocker l(&mtx); - S = s; + if (value == activep) + return; activep = value; + S = s; } emit S->recomputed(); } @@ -98,10 +100,10 @@ bool spline::get_last_value(QPointF& point) double spline::get_value_internal(int x) const { - const float sign = signum(x); + const auto sign = (f)signum(x); x = std::abs(x); - const float ret_ = data[std::min(unsigned(x), value_count - 1)]; - return (double)(sign * clamp(ret_, 0, 1000)); + const auto ret_ = data[std::min(unsigned(x), value_count - 1)]; + return (double)(sign * std::clamp(ret_, (f)0, (f)1000)); } void spline::ensure_in_bounds(const QList<QPointF>& points, int i, f& x, f& y) @@ -129,7 +131,7 @@ void spline::ensure_in_bounds(const QList<QPointF>& points, int i, f& x, f& y) int spline::element_count(const QList<QPointF>& points, double max_input) { - const unsigned sz = (unsigned)points.size(); + const int sz = points.size(); for (int k = sz-1; k >= 0; k--) { const QPointF& pt = points[k]; @@ -139,7 +141,7 @@ int spline::element_count(const QList<QPointF>& points, double max_input) 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(); } @@ -148,14 +150,14 @@ void spline::update_interp_data() const { points_t list = points; ensure_valid(list); - const int sz = list.size(); + int sz = list.size(); if (list.isEmpty()) list.prepend({ max_input(), max_output() }); const double c = bucket_size_coefficient(list); const double c_ = c * c_interp; - const float cf = (float)c, c_f = (float)c_; + const f cf = (f)c, c_f = (f)c_; for (unsigned i = 0; i < value_count; i++) data[i] = magic_fill_value; @@ -165,15 +167,34 @@ void spline::update_interp_data() const const QPointF& pt = list[0]; const double x = pt.x(); const double y = pt.y(); - const unsigned max = clamp(uround(x * c), 0, value_count-1); + const unsigned max = std::clamp((unsigned)iround(x * c), 1u, value_count-1); for (unsigned k = 0; k <= max; k++) - data[k] = float(y * k / max); // no need for bresenham + 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-2) - list.push_front({}); + if (list[0].x() > 1e-6) + { + double zero_pos = 0; + while (list.size() > 1 && list[0].y() <= 1e-6) + { + zero_pos = list[0].x(); + list.pop_front(); + } + list.push_front({zero_pos, 0}); + sz = list.size(); + } // now this is hella expensive due to `c_interp' for (int i = 0; i < sz; i++) @@ -210,21 +231,32 @@ void spline::update_interp_data() const const f t2 = t*t; const f t3 = t*t*t; - const unsigned x = unsigned(f(.5) * cf * (cx[0] + cx[1] * t + cx[2] * t2 + cx[3] * t3)); - const float y = (float)(f(.5) * (cy[0] + cy[1] * t + cy[2] * t2 + cy[3] * t3)); - - int ret = std::fpclassify(y); - if (ret == FP_NAN || ret == FP_INFINITE) + 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; - - if (x < value_count) - data[x] = y; + case FP_ZERO: + case FP_NORMAL: + if (x < value_count) + data[x] = y; + break; + default: + unreachable(); + } } } } - float maxy = (float)max_output(); - float last = 0; + auto maxy = (f)max_output(); + auto last = (f)0; #ifdef __clang__ # pragma clang diagnostic push @@ -235,10 +267,21 @@ void spline::update_interp_data() const { if (data[i] == magic_fill_value) data[i] = last; - data[i] = clamp(data[i], 0, maxy); + 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 @@ -361,11 +404,11 @@ void spline::set_bundle(bundle b, const QString& axis_name, Axis axis) S = s; conn_points = QObject::connect(&s->points, value_::value_changed<QList<QPointF>>(), - ctx.get(), [this] { invalidate_settings(); }, Qt::DirectConnection); + &*ctx, [this] { invalidate_settings(); }, Qt::DirectConnection); conn_maxx = QObject::connect(&s->opts.clamp_x_, value_::value_changed<int>(), - ctx.get(), [this](double) { invalidate_settings(); }, Qt::DirectConnection); + &*ctx, [this](double) { invalidate_settings(); }, Qt::DirectConnection); conn_maxy = QObject::connect(&s->opts.clamp_y_, value_::value_changed<int>(), - ctx.get(), [this](double) { invalidate_settings(); }, Qt::DirectConnection); + &*ctx, [this](double) { invalidate_settings(); }, Qt::DirectConnection); } emit S->recomputed(); @@ -472,7 +515,7 @@ double spline::bucket_size_coefficient(const QList<QPointF>& points) const 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.)); } void spline::disconnect_signals() diff --git a/spline/spline.hpp b/spline/spline.hpp index e4f64069..780442b9 100644 --- a/spline/spline.hpp +++ b/spline/spline.hpp @@ -105,12 +105,12 @@ struct OTR_SPLINE_EXPORT base_spline : base_spline_, spline_modify_mixin, spline class OTR_SPLINE_EXPORT spline : public base_spline { - using f = float; + using f = double; double bucket_size_coefficient(const QList<QPointF>& points) const; void update_interp_data() const; double get_value_internal(int x) const; - static bool sort_fn(const QPointF& one, const QPointF& two); + static bool sort_fn(QPointF one, QPointF two); 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); @@ -125,13 +125,13 @@ class OTR_SPLINE_EXPORT spline : public base_spline std::shared_ptr<QObject> ctx { std::make_shared<QObject>() }; mutable QPointF last_input_value{-1, -1}; - mutable std::vector<float> data = std::vector<float>(value_count, magic_fill_value); + 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; static constexpr unsigned value_count = 8192; - static constexpr float magic_fill_value = -(1 << 24) + 1; + static constexpr f magic_fill_value = -(1 << 24) + 1; static constexpr double c_interp = 5; public: diff --git a/tracker-aruco/CMakeLists.txt b/tracker-aruco/CMakeLists.txt index 69fe7aa1..333edb3e 100644 --- a/tracker-aruco/CMakeLists.txt +++ b/tracker-aruco/CMakeLists.txt @@ -11,6 +11,15 @@ 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) @@ -22,14 +31,14 @@ if(OpenCV_FOUND) 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=17" "-DCXX_STANDARD_REQUIRED=1" + "-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-hier-pfx}") + otr_install_lib("${SDK_ARUCO_LIBPATH}" "${opentrack-libexec}") otr_module(tracker-aruco) target_include_directories(${self} SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS} "${dir}") diff --git a/tracker-aruco/aruco-trackercontrols.ui b/tracker-aruco/aruco-trackercontrols.ui index 9c3bd78a..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>462</width> - <height>221</height> + <width>457</width> + <height>230</height> </rect> </property> <property name="windowTitle"> @@ -64,13 +64,17 @@ <item> <widget class="QFrame" name="frame"> <layout class="QGridLayout" name="gridLayout_4"> - <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> + <item row="4" column="0"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Resolution</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Camera name</string> </property> </widget> </item> @@ -93,7 +97,24 @@ </property> </widget> </item> - <item row="3" column="1"> + <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> + </widget> + </item> + <item row="4" column="1"> <widget class="QComboBox" name="resolution"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> @@ -113,32 +134,21 @@ </item> <item> <property name="text"> + <string>1280x720</string> + </property> + </item> + <item> + <property name="text"> + <string>1920x1080</string> + </property> + </item> + <item> + <property name="text"> <string>Default (not recommended!)</string> </property> </item> </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="2" column="0"> - <widget class="QLabel" name="label_10"> - <property name="text"> - <string>Camera name</string> - </property> - </widget> - </item> <item row="0" column="0"> <widget class="QLabel" name="label_9"> <property name="text"> @@ -146,8 +156,8 @@ </property> </widget> </item> - <item row="1" column="1"> - <widget class="QComboBox" name="cameraFPS"> + <item row="2" column="1"> + <widget class="QComboBox" name="cameraName"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> <horstretch>0</horstretch> @@ -156,7 +166,7 @@ </property> </widget> </item> - <item row="4" column="1"> + <item row="5" column="1"> <widget class="QPushButton" name="camera_settings"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> @@ -169,6 +179,26 @@ </property> </widget> </item> + <item row="3" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>MJPEG</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QCheckBox" name="use_mjpeg"> + <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> diff --git a/tracker-aruco/ftnoir_tracker_aruco.cpp b/tracker-aruco/ftnoir_tracker_aruco.cpp index 0cdf4c2c..5130a889 100644 --- a/tracker-aruco/ftnoir_tracker_aruco.cpp +++ b/tracker-aruco/ftnoir_tracker_aruco.cpp @@ -56,6 +56,8 @@ static const resolution_tuple resolution_choices[] = { { 640, 480 }, { 320, 240 }, + { 1280, 720 }, + { 1920, 1080 }, { 0, 0 } }; @@ -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,8 +103,8 @@ 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); @@ -157,9 +159,7 @@ static int enum_to_fps(int value) bool aruco_tracker::open_camera() { - int rint = s.resolution; - if (rint < 0 || rint >= (int)(sizeof(resolution_choices) / sizeof(resolution_tuple))) - rint = 0; + 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); @@ -180,6 +180,8 @@ bool aruco_tracker::open_camera() if (fps) args.fps = fps; + args.use_mjpeg = s.use_mjpeg; + if (!camera->start(args)) { qDebug() << "aruco tracker: can't open camera"; @@ -226,7 +228,7 @@ void aruco_tracker::draw_ar(bool ok) } char buf[9]; - ::snprintf(buf, sizeof(buf), "Hz: %d", clamp(int(fps), 0, 9999)); + ::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); } @@ -380,6 +382,7 @@ void aruco_tracker::run() if (!res) { + camera_mtx.unlock(); portable::sleep(100); continue; } @@ -389,7 +392,9 @@ void aruco_tracker::run() switch (img.channels) { case 1: - grayscale.setTo(color); break; + grayscale.create(img.height, img.width, CV_8UC1); + color.copyTo(grayscale); + break; case 3: cv::cvtColor(color, grayscale, cv::COLOR_BGR2GRAY); break; @@ -524,6 +529,7 @@ aruco_dialog::aruco_dialog() : tie_setting(s.headpos_x, ui.cx); tie_setting(s.headpos_y, ui.cy); tie_setting(s.headpos_z, ui.cz); + tie_setting(s.use_mjpeg, ui.use_mjpeg); connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK())); connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel())); diff --git a/tracker-aruco/ftnoir_tracker_aruco.h b/tracker-aruco/ftnoir_tracker_aruco.h index 3c50ada0..839be6d5 100644 --- a/tracker-aruco/ftnoir_tracker_aruco.h +++ b/tracker-aruco/ftnoir_tracker_aruco.h @@ -62,6 +62,7 @@ struct settings : opts { 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(); }; diff --git a/tracker-aruco/lang/nl_NL.ts b/tracker-aruco/lang/nl_NL.ts index 87040565..e5a94f3b 100644 --- a/tracker-aruco/lang/nl_NL.ts +++ b/tracker-aruco/lang/nl_NL.ts @@ -59,6 +59,18 @@ <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> diff --git a/tracker-aruco/lang/ru_RU.ts b/tracker-aruco/lang/ru_RU.ts index 57c8170d..639b59ce 100644 --- a/tracker-aruco/lang/ru_RU.ts +++ b/tracker-aruco/lang/ru_RU.ts @@ -59,6 +59,18 @@ <source>Toggle calibration</source> <translation type="unfinished"></translation> </message> + <message> + <source>1280x720</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>MJPEG</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>1920x1080</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>aruco_dialog</name> diff --git a/tracker-aruco/lang/stub.ts b/tracker-aruco/lang/stub.ts index 67cb36b8..f213eb41 100644 --- a/tracker-aruco/lang/stub.ts +++ b/tracker-aruco/lang/stub.ts @@ -59,6 +59,18 @@ <source>Toggle calibration</source> <translation type="unfinished"></translation> </message> + <message> + <source>1280x720</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>MJPEG</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>1920x1080</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>aruco_dialog</name> diff --git a/tracker-aruco/lang/zh_CN.ts b/tracker-aruco/lang/zh_CN.ts index b5093b4e..5d5646b7 100644 --- a/tracker-aruco/lang/zh_CN.ts +++ b/tracker-aruco/lang/zh_CN.ts @@ -1,47 +1,47 @@ <?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><html><head/><body><p>Read the <a href="https://github.com/opentrack/opentrack/wiki/Aruco-tracker"><span style=" text-decoration: underline; color:#0000ff;">wiki page</span></a> and especially the last paragraph before printing markers.</p></body></html></source> - <translation type="unfinished"></translation> + <translation type="unfinished"><html><head/><body><p>阅读此<a href="https://github.com/opentrack/opentrack/wiki/Aruco-tracker"><span style=" text-decoration: underline; color:#0077DD;">Wiki页面</span></a> 特别是打印标记前的最后一段. </p></body></html></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>Camera name</source> - <translation type="unfinished"></translation> + <translation>相机名称</translation> </message> <message> <source>Frames per second</source> - <translation type="unfinished"></translation> + <translation>FPS</translation> </message> <message> <source>Resolution</source> - <translation type="unfinished"></translation> + <translation type="unfinished">分辨率</translation> </message> <message> <source>Camera settings</source> - <translation type="unfinished"></translation> + <translation>相机设置</translation> </message> <message> <source>Head X</source> @@ -57,8 +57,20 @@ </message> <message> <source>Toggle calibration</source> + <translation>切换校准</translation> + </message> + <message> + <source>1280x720</source> + <translation></translation> + </message> + <message> + <source>MJPEG</source> <translation type="unfinished"></translation> </message> + <message> + <source>1920x1080</source> + <translation></translation> + </message> </context> <context> <name>aruco_dialog</name> diff --git a/tracker-easy/CMakeLists.txt b/tracker-easy/CMakeLists.txt index 670b9003..ff537877 100644 --- a/tracker-easy/CMakeLists.txt +++ b/tracker-easy/CMakeLists.txt @@ -5,17 +5,17 @@ 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=17" "-DCXX_STANDARD_REQUIRED=1" + "-DCXX_STANDARD=20" "-DCXX_STANDARD_REQUIRED=1" OUTPUT_VARIABLE krap) if(tracker-easy_ocv-check) - foreach(k video highgui) - otr_install_lib("opencv_${k}" "${opentrack-hier-pfx}") + 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_definitions(-wd4018) + add_compile_options(-wd4018) endif() otr_module(tracker-easy) 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><html><head/><body><p>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.</p></body></html></source> + <translation><html><head/><body><p>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.</p></body></html></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><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">von Stéphane Lenclud</span></p><p>Siehe <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">Dokumentation auf GitHub</span></a></p></body></html></translation> + </message> +</context> +</TS> diff --git a/tracker-easy/lang/nl_NL.ts b/tracker-easy/lang/nl_NL.ts index 9c4e1843..775ec4d9 100644 --- a/tracker-easy/lang/nl_NL.ts +++ b/tracker-easy/lang/nl_NL.ts @@ -4,17 +4,13 @@ <context> <name>EasyTracker::Metadata</name> <message> - <source>Easy Tracker 1.0</source> + <source>Easy Tracker 1.1</source> <translation type="unfinished"></translation> </message> </context> <context> <name>UICPTClientControls</name> <message> - <source>PointTracker Settings</source> - <translation type="unfinished"></translation> - </message> - <message> <source>Camera</source> <translation type="unfinished"></translation> </message> @@ -151,79 +147,103 @@ <translation type="unfinished"></translation> </message> <message> - <source>Top Right</source> + <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Top</source> + <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Top Left</source> + <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Left</source> + <source>Auto center</source> <translation type="unfinished"></translation> </message> <message> - <source>Vertex count</source> + <source><html><head/><body><p>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.</p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Three vertices</source> + <source>Auto center timeout</source> <translation type="unfinished"></translation> </message> <message> - <source>Four vertices</source> + <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>Five vertices</source> + <source> ms</source> <translation type="unfinished"></translation> </message> <message> - <source>Center</source> + <source>Easy Tracker Settings</source> <translation type="unfinished"></translation> </message> <message> - <source>Right</source> + <source>Model type:</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> + <source>Hat three vertices</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> + <source>Hat four vertices</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> + <source>Hat five vertices</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.0</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> + <source>Clip three vertices</source> <translation type="unfinished"></translation> </message> <message> - <source>Auto center</source> + <source>Vertices: </source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p>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.</p></body></html></source> + <source>Top:</source> <translation type="unfinished"></translation> </message> <message> - <source>Auto center timeout</source> + <source>Right:</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> + <source>Left:</source> <translation type="unfinished"></translation> </message> <message> - <source> ms</source> + <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><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> <translation type="unfinished"></translation> </message> </context> diff --git a/tracker-easy/lang/ru_RU.ts b/tracker-easy/lang/ru_RU.ts index 6499415f..2ae262a5 100644 --- a/tracker-easy/lang/ru_RU.ts +++ b/tracker-easy/lang/ru_RU.ts @@ -4,17 +4,13 @@ <context> <name>EasyTracker::Metadata</name> <message> - <source>Easy Tracker 1.0</source> + <source>Easy Tracker 1.1</source> <translation type="unfinished"></translation> </message> </context> <context> <name>UICPTClientControls</name> <message> - <source>PointTracker Settings</source> - <translation>Настройки PointTracker</translation> - </message> - <message> <source>Camera</source> <translation>Камера</translation> </message> @@ -151,79 +147,103 @@ <translation type="unfinished"></translation> </message> <message> - <source>Top Right</source> + <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Top</source> + <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Top Left</source> + <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Left</source> + <source>Auto center</source> <translation type="unfinished"></translation> </message> <message> - <source>Vertex count</source> + <source><html><head/><body><p>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.</p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Three vertices</source> + <source>Auto center timeout</source> <translation type="unfinished"></translation> </message> <message> - <source>Four vertices</source> + <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>Five vertices</source> + <source> ms</source> <translation type="unfinished"></translation> </message> <message> - <source>Center</source> + <source>Easy Tracker Settings</source> <translation type="unfinished"></translation> </message> <message> - <source>Right</source> + <source>Model type:</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> + <source>Hat three vertices</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> + <source>Hat four vertices</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> + <source>Hat five vertices</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.0</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> + <source>Clip three vertices</source> <translation type="unfinished"></translation> </message> <message> - <source>Auto center</source> + <source>Vertices: </source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p>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.</p></body></html></source> + <source>Top:</source> <translation type="unfinished"></translation> </message> <message> - <source>Auto center timeout</source> + <source>Right:</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> + <source>Left:</source> <translation type="unfinished"></translation> </message> <message> - <source> ms</source> + <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><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> <translation type="unfinished"></translation> </message> </context> diff --git a/tracker-easy/lang/stub.ts b/tracker-easy/lang/stub.ts index 7b6facec..b10f5885 100644 --- a/tracker-easy/lang/stub.ts +++ b/tracker-easy/lang/stub.ts @@ -4,17 +4,13 @@ <context> <name>EasyTracker::Metadata</name> <message> - <source>Easy Tracker 1.0</source> + <source>Easy Tracker 1.1</source> <translation type="unfinished"></translation> </message> </context> <context> <name>UICPTClientControls</name> <message> - <source>PointTracker Settings</source> - <translation type="unfinished"></translation> - </message> - <message> <source>Camera</source> <translation type="unfinished"></translation> </message> @@ -151,79 +147,103 @@ <translation type="unfinished"></translation> </message> <message> - <source>Top Right</source> + <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Top</source> + <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Top Left</source> + <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Left</source> + <source>Auto center</source> <translation type="unfinished"></translation> </message> <message> - <source>Vertex count</source> + <source><html><head/><body><p>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.</p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Three vertices</source> + <source>Auto center timeout</source> <translation type="unfinished"></translation> </message> <message> - <source>Four vertices</source> + <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>Five vertices</source> + <source> ms</source> <translation type="unfinished"></translation> </message> <message> - <source>Center</source> + <source>Easy Tracker Settings</source> <translation type="unfinished"></translation> </message> <message> - <source>Right</source> + <source>Model type:</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> + <source>Hat three vertices</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> + <source>Hat four vertices</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> + <source>Hat five vertices</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.0</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> + <source>Clip three vertices</source> <translation type="unfinished"></translation> </message> <message> - <source>Auto center</source> + <source>Vertices: </source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p>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.</p></body></html></source> + <source>Top:</source> <translation type="unfinished"></translation> </message> <message> - <source>Auto center timeout</source> + <source>Right:</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> + <source>Left:</source> <translation type="unfinished"></translation> </message> <message> - <source> ms</source> + <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><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> <translation type="unfinished"></translation> </message> </context> diff --git a/tracker-easy/lang/zh_CN.ts b/tracker-easy/lang/zh_CN.ts index bbd8aff1..71fc6368 100644 --- a/tracker-easy/lang/zh_CN.ts +++ b/tracker-easy/lang/zh_CN.ts @@ -4,17 +4,13 @@ <context> <name>EasyTracker::Metadata</name> <message> - <source>Easy Tracker 1.0</source> + <source>Easy Tracker 1.1</source> <translation type="unfinished"></translation> </message> </context> <context> <name>UICPTClientControls</name> <message> - <source>PointTracker Settings</source> - <translation>PointTracker设置</translation> - </message> - <message> <source>Camera</source> <translation>摄像头</translation> </message> @@ -151,79 +147,103 @@ <translation type="unfinished"></translation> </message> <message> - <source>Top Right</source> + <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Top</source> + <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Top Left</source> + <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Left</source> + <source>Auto center</source> <translation type="unfinished"></translation> </message> <message> - <source>Vertex count</source> + <source><html><head/><body><p>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.</p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <source>Three vertices</source> + <source>Auto center timeout</source> <translation type="unfinished"></translation> </message> <message> - <source>Four vertices</source> + <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>Five vertices</source> + <source> ms</source> <translation type="unfinished"></translation> </message> <message> - <source>Center</source> + <source>Easy Tracker Settings</source> <translation type="unfinished"></translation> </message> <message> - <source>Right</source> + <source>Model type:</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> + <source>Hat three vertices</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> + <source>Hat four vertices</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> + <source>Hat five vertices</source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.0</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> + <source>Clip three vertices</source> <translation type="unfinished"></translation> </message> <message> - <source>Auto center</source> + <source>Vertices: </source> <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p>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.</p></body></html></source> + <source>Top:</source> <translation type="unfinished"></translation> </message> <message> - <source>Auto center timeout</source> + <source>Right:</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> + <source>Left:</source> <translation type="unfinished"></translation> </message> <message> - <source> ms</source> + <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><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> <translation type="unfinished"></translation> </message> </context> diff --git a/tracker-easy/module.cpp b/tracker-easy/module.cpp index 3a9df22b..8fcfeae1 100644 --- a/tracker-easy/module.cpp +++ b/tracker-easy/module.cpp @@ -6,7 +6,7 @@ namespace EasyTracker { - QString Metadata::name() { return tr("Easy Tracker 1.0"); } + QString Metadata::name() { return tr("Easy Tracker 1.1"); } QIcon Metadata::icon() { return QIcon(":/Resources/easy-tracker-logo.png"); } } diff --git a/tracker-easy/preview.cpp b/tracker-easy/preview.cpp index 1dad7d84..97c8aeaf 100644 --- a/tracker-easy/preview.cpp +++ b/tracker-easy/preview.cpp @@ -9,6 +9,7 @@ #include "preview.h" #include "compat/math.hpp" +#include "compat/macros.h" #include <opencv2/imgproc.hpp> #include <QDebug> diff --git a/tracker-easy/settings.h b/tracker-easy/settings.h index b0f14417..4141ebe0 100644 --- a/tracker-easy/settings.h +++ b/tracker-easy/settings.h @@ -30,6 +30,7 @@ namespace EasyTracker { 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 }; @@ -38,7 +39,10 @@ namespace EasyTracker { 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 }; diff --git a/tracker-easy/tracker-easy-dialog.cpp b/tracker-easy/tracker-easy-dialog.cpp index 4a49e194..b0870b50 100644 --- a/tracker-easy/tracker-easy-dialog.cpp +++ b/tracker-easy/tracker-easy-dialog.cpp @@ -68,6 +68,19 @@ namespace EasyTracker 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); @@ -87,10 +100,12 @@ namespace EasyTracker 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++) @@ -111,20 +126,49 @@ namespace EasyTracker 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(); } - } diff --git a/tracker-easy/tracker-easy-settings.ui b/tracker-easy/tracker-easy-settings.ui index 451b3e54..d6cc86b6 100644 --- a/tracker-easy/tracker-easy-settings.ui +++ b/tracker-easy/tracker-easy-settings.ui @@ -9,8 +9,8 @@ <rect> <x>0</x> <y>0</y> - <width>465</width> - <height>764</height> + <width>328</width> + <height>923</height> </rect> </property> <property name="sizePolicy"> @@ -20,7 +20,7 @@ </sizepolicy> </property> <property name="windowTitle"> - <string>PointTracker Settings</string> + <string>Easy Tracker Settings</string> </property> <property name="windowIcon"> <iconset> @@ -32,24 +32,8 @@ <property name="autoFillBackground"> <bool>false</bool> </property> - <layout class="QGridLayout" name="gridLayout_9"> - <property name="sizeConstraint"> - <enum>QLayout::SetFixedSize</enum> - </property> - <item row="7" column="0"> - <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> - <item row="0" column="0"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item alignment="Qt::AlignTop"> <widget class="QTabWidget" name="tabWidget"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> @@ -61,7 +45,7 @@ <locale language="English" country="UnitedStates"/> </property> <property name="currentIndex"> - <number>0</number> + <number>2</number> </property> <widget class="QWidget" name="tabTracker"> <attribute name="title"> @@ -548,13 +532,13 @@ <item> <widget class="QGroupBox" name="groupBoxCustomModelType"> <property name="title"> - <string>Vertex count</string> + <string>Model type:</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout"> + <layout class="QVBoxLayout" name="verticalLayout_4"> <item> <widget class="QRadioButton" name="iRadioButtonCustomModelThree"> <property name="text"> - <string>Three vertices</string> + <string>Hat three vertices</string> </property> <property name="checked"> <bool>true</bool> @@ -564,14 +548,21 @@ <item> <widget class="QRadioButton" name="iRadioButtonCustomModelFour"> <property name="text"> - <string>Four vertices</string> + <string>Hat four vertices</string> </property> </widget> </item> <item> <widget class="QRadioButton" name="iRadioButtonCustomModelFive"> <property name="text"> - <string>Five vertices</string> + <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> @@ -581,7 +572,7 @@ <item> <widget class="QGroupBox" name="groupBox"> <property name="title"> - <string/> + <string>Vertices: </string> </property> <property name="flat"> <bool>false</bool> @@ -614,7 +605,7 @@ <item> <widget class="QGroupBox" name="iGroupBoxTop"> <property name="title"> - <string>Top</string> + <string>Top:</string> </property> <layout class="QHBoxLayout" name="horizontalLayout_2"> <property name="topMargin"> @@ -665,7 +656,7 @@ <item> <widget class="QGroupBox" name="iGroupBoxRight"> <property name="title"> - <string>Right</string> + <string>Right:</string> </property> <layout class="QHBoxLayout" name="horizontalLayout_3"> <item> @@ -713,7 +704,7 @@ <item> <widget class="QGroupBox" name="iGroupBoxLeft"> <property name="title"> - <string>Left</string> + <string>Left:</string> </property> <layout class="QHBoxLayout" name="horizontalLayout_4"> <item> @@ -761,7 +752,7 @@ <item> <widget class="QGroupBox" name="iGroupBoxCenter"> <property name="title"> - <string>Center</string> + <string>Center:</string> </property> <layout class="QHBoxLayout" name="horizontalLayout_5"> <item> @@ -809,7 +800,7 @@ <item> <widget class="QGroupBox" name="iGroupBoxTopRight"> <property name="title"> - <string>Top Right</string> + <string>Top right:</string> </property> <layout class="QHBoxLayout" name="horizontalLayout_6"> <item> @@ -857,7 +848,7 @@ <item> <widget class="QGroupBox" name="iGroupBoxTopLeft"> <property name="title"> - <string>Top Left</string> + <string>Top left:</string> </property> <layout class="QHBoxLayout" name="horizontalLayout_7"> <item> @@ -902,6 +893,159 @@ </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"> @@ -912,7 +1056,7 @@ <item row="0" column="0"> <widget class="QLabel" name="label_10"> <property name="text"> - <string><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.0</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></string> + <string><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></string> </property> <property name="alignment"> <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> @@ -939,10 +1083,22 @@ </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>tabWidget</tabstop> <tabstop>camdevice_combo</tabstop> <tabstop>res_x_spin</tabstop> <tabstop>res_y_spin</tabstop> diff --git a/tracker-easy/tracker-easy.cpp b/tracker-easy/tracker-easy.cpp index 31c604c9..7046a918 100644 --- a/tracker-easy/tracker-easy.cpp +++ b/tracker-easy/tracker-easy.cpp @@ -56,8 +56,8 @@ namespace EasyTracker { opencv_init(); - connect(iSettings.b.get(), &bundle_::saving, this, &Tracker::CheckCamera, Qt::DirectConnection); - connect(iSettings.b.get(), &bundle_::reloading, this, &Tracker::CheckCamera, Qt::DirectConnection); + 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); @@ -154,8 +154,13 @@ namespace EasyTracker // 5 - Radial fourth order // 6 - Radial fifth order // 7 - Radial sixth order - for (unsigned k = 0; k < 8; k++) - iDistCoeffsMatrix(k) = (double)iCameraInfo.dist_c[k]; + // + // 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]; } @@ -165,10 +170,15 @@ namespace EasyTracker { MatchFiveVertices(aTopIndex, aRightIndex, aLeftIndex, aTopRight, aTopLeft); } - else + else if (!iSettings.iClipModelThree) { MatchThreeOrFourVertices(aTopIndex, aRightIndex, aLeftIndex, aCenterIndex); } + else + { + // Clip model + MatchClipVertices(aTopIndex, aRightIndex, aLeftIndex); + } } @@ -183,7 +193,7 @@ namespace EasyTracker // 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 < iPoints.size(); i++) + for (int i = 0; i < (int)iPoints.size(); i++) { if (iPoints[i].y < minY) { @@ -195,7 +205,7 @@ namespace EasyTracker // Find right most point int maxX = 0; - for (int i = 0; i < iPoints.size(); i++) + for (int i = 0; i < (int)iPoints.size(); i++) { // Excluding top most point if (i != vertexIndices[VertexPosition::Top] && iPoints[i].x > maxX) @@ -208,7 +218,7 @@ namespace EasyTracker // Find left most point int minX = std::numeric_limits<int>::max(); - for (int i = 0; i < iPoints.size(); i++) + 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) @@ -281,7 +291,7 @@ namespace EasyTracker // 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 < iPoints.size(); i++) + for (int i = 0; i < (int)iPoints.size(); i++) { if (iPoints[i].y < minY) { @@ -294,7 +304,7 @@ namespace EasyTracker int maxX = 0; // Find right most point - for (int i = 0; i < iPoints.size(); i++) + for (int i = 0; i < (int)iPoints.size(); i++) { // Excluding top most point if (i != aTopIndex && iPoints[i].x > maxX) @@ -306,7 +316,7 @@ namespace EasyTracker // Find left most point int minX = std::numeric_limits<int>::max(); - for (int i = 0; i < iPoints.size(); i++) + 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) @@ -317,7 +327,7 @@ namespace EasyTracker } // Find center point, the last one - for (int i = 0; i < iPoints.size(); i++) + for (int i = 0; i < (int)iPoints.size(); i++) { // Excluding the three points we already have if (i != aTopIndex && i != aRightIndex && i != aLeftIndex) @@ -337,7 +347,56 @@ namespace EasyTracker } } - + /** + */ + 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]); + } + + /// /// @@ -720,18 +779,29 @@ namespace EasyTracker // We are converting them from millimeters to centimeters. // TODO: Need to support clip too. That's cap only for now. iModel.clear(); - 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) + if (!iSettings.iClipModelThree) { - iModel.push_back(cv::Point3f(iSettings.iVertexCenterX / 10.0, iSettings.iVertexCenterY / 10.0, iSettings.iVertexCenterZ / 10.0)); // Center + 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 if (iSettings.iCustomModelFive) + else { - 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 + // 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"; @@ -772,8 +842,8 @@ namespace EasyTracker widget = std::make_unique<video_widget>(video_frame); layout = std::make_unique<QHBoxLayout>(video_frame); layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(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(); diff --git a/tracker-easy/tracker-easy.h b/tracker-easy/tracker-easy.h index 0b51f9c7..4510fc7d 100644 --- a/tracker-easy/tracker-easy.h +++ b/tracker-easy/tracker-easy.h @@ -77,6 +77,7 @@ namespace EasyTracker 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); // 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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Please make sure the Eyeware Beam application is running and tracking is active.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To download, visit <a href="https://beam.eyeware.tech/opentrack"><span style=" text-decoration: underline; color:#0000ff;">https://beam.eyeware.tech/opentrack</span></a>.</p></body></html></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 Binary files differnew file mode 100644 index 00000000..6a611cac --- /dev/null +++ b/tracker-eyeware-beam/eyeware_beam_logo.png 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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Please make sure the Eyeware Beam application is running and tracking is active.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To download, visit <a href="https://beam.eyeware.tech/opentrack"><span style=" text-decoration: underline; color:#0000ff;">https://beam.eyeware.tech/opentrack</span></a>.</p></body></html></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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Please make sure the Eyeware Beam application is running and tracking is active.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To download, visit <a href="https://beam.eyeware.tech/opentrack"><span style=" text-decoration: underline; color:#0000ff;">https://beam.eyeware.tech/opentrack</span></a>.</p></body></html></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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Please make sure the Eyeware Beam application is running and tracking is active.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To download, visit <a href="https://beam.eyeware.tech/opentrack"><span style=" text-decoration: underline; color:#0000ff;">https://beam.eyeware.tech/opentrack</span></a>.</p></body></html></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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Please make sure the Eyeware Beam application is running and tracking is active.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To download, visit <a href="https://beam.eyeware.tech/opentrack"><span style=" text-decoration: underline; color:#0000ff;">https://beam.eyeware.tech/opentrack</span></a>.</p></body></html></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 c9d374ed..3950af0c 100644 --- a/tracker-freepie-udp/ftnoir_tracker_freepie-udp.cpp +++ b/tracker-freepie-udp/ftnoir_tracker_freepie-udp.cpp @@ -40,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}; 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/zh_CN.ts b/tracker-freepie-udp/lang/zh_CN.ts index 1072784a..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> diff --git a/tracker-fusion/fusion.cpp b/tracker-fusion/fusion.cpp index 47dc0d13..fba38d3d 100644 --- a/tracker-fusion/fusion.cpp +++ b/tracker-fusion/fusion.cpp @@ -14,6 +14,7 @@ #include <QDebug> #include <QMessageBox> #include <QApplication> +#include <cassert> static const char* own_name = "fusion"; @@ -114,7 +115,7 @@ module_status fusion_tracker::start_tracker(QFrame* frame) other_frame->setFixedSize(320, 240); // XXX magic frame size other_frame->setVisible(false); - rot_tracker->start_tracker(other_frame.get()); + rot_tracker->start_tracker(&*other_frame); } end: 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's required that they'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/zh_CN.ts b/tracker-fusion/lang/zh_CN.ts index 7aed6201..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> diff --git a/tracker-hatire/CMakeLists.txt b/tracker-hatire/CMakeLists.txt index a73a471c..01dd52b9 100644 --- a/tracker-hatire/CMakeLists.txt +++ b/tracker-hatire/CMakeLists.txt @@ -2,4 +2,7 @@ if(Qt5SerialPort_FOUND) otr_module(tracker-hatire) 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 aa84758b..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>488</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> @@ -1136,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"> @@ -1163,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"> @@ -1173,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> @@ -1190,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"> @@ -1197,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> @@ -1214,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> @@ -1371,6 +1391,7 @@ p, li { white-space: pre-wrap; } </layout> </widget> <tabstops> + <tabstop>tabWidget</tabstop> <tabstop>cbSerialPort</tabstop> <tabstop>btnZero</tabstop> <tabstop>btnReset</tabstop> @@ -1393,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 b53a9213..9948c30e 100644 --- a/tracker-hatire/ftnoir_tracker_hat.cpp +++ b/tracker-hatire/ftnoir_tracker_hat.cpp @@ -122,7 +122,7 @@ 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 { diff --git a/tracker-hatire/ftnoir_tracker_hat.h b/tracker-hatire/ftnoir_tracker_hat.h index b2300556..16e39c7d 100644 --- a/tracker-hatire/ftnoir_tracker_hat.h +++ b/tracker-hatire/ftnoir_tracker_hat.h @@ -32,6 +32,7 @@ public: void send_serial_command(const QByteArray& x); hatire_thread t; + private: TArduinoData ArduinoData {}, HAT {}; QByteArray Begin; @@ -42,8 +43,6 @@ private: int frame_cnt = 0; std::atomic<int> CptError { 0 }; - - static inline QByteArray to_latin1(const QString& str) { return str.toLatin1(); } }; class hatire_metadata : public Metadata diff --git a/tracker-hatire/ftnoir_tracker_hat_dialog.cpp b/tracker-hatire/ftnoir_tracker_hat_dialog.cpp index 7cb25d18..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); diff --git a/tracker-hatire/ftnoir_tracker_hat_settings.h b/tracker-hatire/ftnoir_tracker_hat_settings.h index a41f906d..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; + value<bool> BigEndian, EnableLogging, pDTR; value<QString> QSerialPortName; @@ -63,6 +63,7 @@ struct TrackerSettings : opts DelaySeq(b, "after-start-delay", 0), BigEndian(b, "is-big-endian", false), EnableLogging(b, "enable-logging", 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 a1e613b0..af8cbd2c 100644 --- a/tracker-hatire/lang/nl_NL.ts +++ b/tracker-hatire/lang/nl_NL.ts @@ -269,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> diff --git a/tracker-hatire/lang/ru_RU.ts b/tracker-hatire/lang/ru_RU.ts index f7f3024d..16052702 100644 --- a/tracker-hatire/lang/ru_RU.ts +++ b/tracker-hatire/lang/ru_RU.ts @@ -269,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> diff --git a/tracker-hatire/lang/stub.ts b/tracker-hatire/lang/stub.ts index 0d22bf81..90c8e616 100644 --- a/tracker-hatire/lang/stub.ts +++ b/tracker-hatire/lang/stub.ts @@ -269,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> diff --git a/tracker-hatire/lang/zh_CN.ts b/tracker-hatire/lang/zh_CN.ts index 0d22bf81..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> @@ -269,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> diff --git a/tracker-hatire/thread.cpp b/tracker-hatire/thread.cpp index 4938e77f..7d61a90a 100644 --- a/tracker-hatire/thread.cpp +++ b/tracker-hatire/thread.cpp @@ -71,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(); @@ -111,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"); @@ -120,7 +120,7 @@ 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); @@ -168,6 +168,7 @@ 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) ) { @@ -200,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) { @@ -208,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) @@ -241,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 :"); diff --git a/tracker-hatire/thread.hpp b/tracker-hatire/thread.hpp index 5cecbdce..f6bd8d49 100644 --- a/tracker-hatire/thread.hpp +++ b/tracker-hatire/thread.hpp @@ -50,7 +50,6 @@ class hatire_thread : public QThread 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 b99c1d7a..48471765 100644 --- a/tracker-hydra/CMakeLists.txt +++ b/tracker-hydra/CMakeLists.txt @@ -22,7 +22,7 @@ if(SDK_HYDRA AND opentrack-intel) 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-hier-pfx} PERMISSIONS ${opentrack-perms-exec}) + install(FILES "${dll}" DESTINATION ${opentrack-libexec} PERMISSIONS ${opentrack-perms-exec}) target_link_libraries(opentrack-tracker-hydra "${lib}") else() if(APPLE) @@ -31,13 +31,13 @@ if(SDK_HYDRA AND opentrack-intel) set(soext "dylib") set(plat "osx") else() # assume Linux - set(dest "${opentrack-hier-pfx}") + set(dest "${opentrack-libexec}") set(part "release") set(soext "so") set(plat "linux") endif() 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-hier-pfx} PERMISSIONS ${opentrack-perms-exec}) + install(FILES "${SDK_HYDRA}/lib/${plat}${six4}/${part}/${lib}" DESTINATION ${opentrack-libexec} PERMISSIONS ${opentrack-perms-exec}) endif() endif() 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 bd7e51ed..940abfd1 100644 --- a/tracker-joystick/ftnoir_tracker_joystick.cpp +++ b/tracker-joystick/ftnoir_tracker_joystick.cpp @@ -56,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/lang/zh_CN.ts b/tracker-joystick/lang/zh_CN.ts index a169a05c..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> diff --git a/tracker-kinect-face/CMakeLists.txt b/tracker-kinect-face/CMakeLists.txt index 7ab62df2..d205a764 100644 --- a/tracker-kinect-face/CMakeLists.txt +++ b/tracker-kinect-face/CMakeLists.txt @@ -30,16 +30,16 @@ if (WIN32 AND opentrack-intel) target_link_libraries(${self} opentrack-video) # Install Kinect Face DLL - install(FILES "${SDK_KINECT20}/Redist/Face/${kinect-arch-dir}/Kinect20.Face.dll" DESTINATION "${opentrack-hier-pfx}" PERMISSIONS ${opentrack-perms-exec}) + 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-hier-pfx}") + 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-hier-pfx}" - PERMISSIONS ${opentrack-perms-exec} - ) + #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 diff --git a/tracker-kinect-face/camera_kinect_ir.cpp b/tracker-kinect-face/camera_kinect_ir.cpp index 81357a6d..3a33fd14 100644 --- a/tracker-kinect-face/camera_kinect_ir.cpp +++ b/tracker-kinect-face/camera_kinect_ir.cpp @@ -48,7 +48,12 @@ namespace Kinect { std::vector<QString> CamerasProvider::camera_names() const { - if (camera_name_to_index("Kinect V2 Video Sensor") != -1) + 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. @@ -232,6 +237,7 @@ namespace Kinect { // Release previous frame if any SafeRelease(iInfraredFrame); + Sleep(34); // FIXME HRESULT hr = iInfraredFrameReader->AcquireLatestFrame(&iInfraredFrame); if (SUCCEEDED(hr)) diff --git a/tracker-kinect-face/kinect_face_settings.h b/tracker-kinect-face/kinect_face_settings.h index 40af1eb7..2c5cc55f 100644 --- a/tracker-kinect-face/kinect_face_settings.h +++ b/tracker-kinect-face/kinect_face_settings.h @@ -8,7 +8,6 @@ #pragma once #include "ui_kinect_face_settings.h" -#include "compat/macros.hpp" #include "api/plugin-api.hpp" class KinectFaceSettings : public ITrackerDialog diff --git a/tracker-kinect-face/kinect_face_tracker.cpp b/tracker-kinect-face/kinect_face_tracker.cpp index e7b77133..4bab2eef 100644 --- a/tracker-kinect-face/kinect_face_tracker.cpp +++ b/tracker-kinect-face/kinect_face_tracker.cpp @@ -136,8 +136,8 @@ module_status KinectFaceTracker::start_tracker(QFrame* aFrame) iVideoWidget = std::make_unique<video_widget>(aFrame); iLayout = std::make_unique<QHBoxLayout>(aFrame); iLayout->setContentsMargins(0, 0, 0, 0); - iLayout->addWidget(iVideoWidget.get()); - aFrame->setLayout(iLayout.get()); + iLayout->addWidget(&*iVideoWidget); + aFrame->setLayout(&*iLayout); //video_widget->resize(video_frame->width(), video_frame->height()); aFrame->show(); diff --git a/tracker-kinect-face/kinect_face_tracker.h b/tracker-kinect-face/kinect_face_tracker.h index 8070c519..83b58d71 100644 --- a/tracker-kinect-face/kinect_face_tracker.h +++ b/tracker-kinect-face/kinect_face_tracker.h @@ -12,7 +12,6 @@ #include "api/plugin-api.hpp" #include "compat/timer.hpp" -#include "compat/macros.hpp" #include "video/video-widget.hpp" // Kinect Header files diff --git a/tracker-kinect-face/lang/zh_CN.ts b/tracker-kinect-face/lang/zh_CN.ts index de0e4f95..1e1b55d1 100644 --- a/tracker-kinect-face/lang/zh_CN.ts +++ b/tracker-kinect-face/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>KinectFaceMetadata</name> <message> 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/trackmouse/lang/stub.ts b/tracker-linux-joystick/lang/stub.ts index 968e31c5..12dc1400 100644 --- a/trackmouse/lang/stub.ts +++ b/tracker-linux-joystick/lang/stub.ts @@ -2,72 +2,84 @@ <!DOCTYPE TS> <TS version="2.1"> <context> - <name>main_window</name> + <name>UILinuxJoystickControls</name> <message> - <source>The Octopus is sad</source> + <source>Tracker settings</source> <translation type="unfinished"></translation> </message> <message> - <source> :: </source> + <source>Device</source> <translation type="unfinished"></translation> </message> <message> - <source>Check permissions for your .ini directory: - -%1"%2 - -Exiting now.</source> + <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> -</context> -<context> - <name>window</name> <message> - <source>trackmouse prototype</source> + <source>Joystick axis #2</source> <translation type="unfinished"></translation> </message> <message> - <source>Keyboard shortcuts</source> + <source>Joystick axis #3</source> <translation type="unfinished"></translation> </message> <message> - <source>start/stop tracking</source> + <source>Joystick axis #4</source> <translation type="unfinished"></translation> </message> <message> - <source>Alt+F10</source> + <source>Joystick axis #5</source> <translation type="unfinished"></translation> </message> <message> - <source>center</source> + <source>Joystick axis #6</source> <translation type="unfinished"></translation> </message> <message> - <source>Alt+F11</source> + <source>Joystick axis #7</source> <translation type="unfinished"></translation> </message> <message> - <source>Alt+F12</source> + <source>Joystick axis #8</source> <translation type="unfinished"></translation> </message> <message> - <source>Sensitivity</source> + <source>X</source> <translation type="unfinished"></translation> </message> <message> - <source>100%</source> + <source>Y</source> <translation type="unfinished"></translation> </message> <message> - <source>Start</source> + <source>Z</source> <translation type="unfinished"></translation> </message> <message> - <source>Stop</source> + <source>Yaw</source> <translation type="unfinished"></translation> </message> <message> - <source>freeze toggle</source> + <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> 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 Binary files differnew file mode 100644 index 00000000..1a10c53c --- /dev/null +++ b/tracker-neuralnet/images/neuralnet.png 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'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><the pose net file></source> + <translation><die pose netzwerk datei></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'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><the pose net file></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'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><the pose net file></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'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><the pose net file></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'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><the pose net file></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 Binary files differnew file mode 100644 index 00000000..c128f89d --- /dev/null +++ b/tracker-neuralnet/models/head-localizer.onnx diff --git a/tracker-neuralnet/models/head-pose-0.2-big.onnx b/tracker-neuralnet/models/head-pose-0.2-big.onnx Binary files differnew file mode 100644 index 00000000..e53fd831 --- /dev/null +++ b/tracker-neuralnet/models/head-pose-0.2-big.onnx diff --git a/tracker-neuralnet/models/head-pose-0.2-small.onnx b/tracker-neuralnet/models/head-pose-0.2-small.onnx Binary files differnew file mode 100644 index 00000000..f2b64219 --- /dev/null +++ b/tracker-neuralnet/models/head-pose-0.2-small.onnx diff --git a/tracker-neuralnet/models/head-pose-0.3-big-quantized.onnx b/tracker-neuralnet/models/head-pose-0.3-big-quantized.onnx Binary files differnew file mode 100644 index 00000000..7f875c63 --- /dev/null +++ b/tracker-neuralnet/models/head-pose-0.3-big-quantized.onnx 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><the pose net file></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 Binary files differnew file mode 100644 index 00000000..42c069b9 --- /dev/null +++ b/tracker-neuralnet/redist/vcomp140.dll 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 078cd4bc..0c2e9ce3 100644 --- a/tracker-pt/CMakeLists.txt +++ b/tracker-pt/CMakeLists.txt @@ -1,9 +1,12 @@ 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(${self} SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS}) - target_link_libraries(${self} opencv_imgproc opentrack-cv opencv_core opentrack-video) + 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 061f5351..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><html><head/><body><p>For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.</p></body></html></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> @@ -420,21 +386,125 @@ <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> @@ -474,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> @@ -550,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> @@ -577,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"> @@ -599,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"> @@ -1323,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"> @@ -1360,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"> @@ -1381,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> @@ -1396,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> @@ -1408,6 +1798,14 @@ 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"/> diff --git a/tracker-pt/ftnoir_tracker_pt.cpp b/tracker-pt/ftnoir_tracker_pt.cpp index 194fd423..0f8495d9 100644 --- a/tracker-pt/ftnoir_tracker_pt.cpp +++ b/tracker-pt/ftnoir_tracker_pt.cpp @@ -6,6 +6,7 @@ * copyright notice and this permission notice appear in all copies. */ +#undef NDEBUG #include "ftnoir_tracker_pt.h" #include "pt-api.hpp" #include "cv/init.hpp" @@ -13,7 +14,9 @@ #include "compat/math-imports.hpp" #include "compat/check-visible.hpp" #include "compat/thread-name.hpp" +#include "compat/qt-dpi.hpp" +#include <cassert> #include <QHBoxLayout> #include <QDebug> #include <QFile> @@ -27,17 +30,11 @@ 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() } { opencv_init(); - connect(s.b.get(), &bundle_::saving, this, &Tracker_PT::maybe_reopen_camera, Qt::DirectConnection); - connect(s.b.get(), &bundle_::reloading, this, &Tracker_PT::maybe_reopen_camera, Qt::DirectConnection); - - connect(&s.fov, value_::value_changed<int>(), this, &Tracker_PT::set_fov, Qt::DirectConnection); - set_fov(s.fov); + connect(&*s.b, &bundle_::saving, this, [this]{ reopen_camera_flag = true; }, Qt::DirectConnection); } Tracker_PT::~Tracker_PT() @@ -45,24 +42,41 @@ Tracker_PT::~Tracker_PT() requestInterruption(); wait(); - QMutexLocker l(&camera_mtx); - camera->stop(); + if (camera) + camera->stop(); +} + +bool Tracker_PT::check_camera() +{ + 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; } void Tracker_PT::run() { portable::set_curthread_name("tracker/pt"); - if (!maybe_reopen_camera()) - return; - while(!isInterruptionRequested()) { + if (!check_camera()) + break; + pt_camera_info info; bool new_frame = false; { - QMutexLocker l(&camera_mtx); + camera->set_fov(s.fov); std::tie(new_frame, info) = camera->get_frame(*frame); } @@ -70,10 +84,10 @@ void Tracker_PT::run() { const bool preview_visible = check_is_visible(); - if (preview_visible) - *preview_frame = *frame; + if (preview_visible && !widget->fresh()) + preview_frame->set_last_frame(*frame); - point_extractor->extract_points(*frame, *preview_frame, points); + 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; @@ -85,14 +99,9 @@ void Tracker_PT::run() if (success) { - int dynamic_pose_ms = s.dynamic_pose && s.active_model_panel != PointModel::Clip - ? s.init_phase_timeout - : 0; - - point_tracker.track(points, - PointModel(s), - info, - dynamic_pose_ms); + 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); } @@ -100,7 +109,7 @@ void Tracker_PT::run() X_CM = point_tracker.pose(); } - if (preview_visible) + 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)); @@ -111,44 +120,31 @@ void Tracker_PT::run() preview_frame->draw_head_center((p[0] * fx) / p[2], (p[1] * fx) / p[2]); widget->update_image(preview_frame->get_bitmap()); - - auto [ w, h ] = widget->preview_size(); - if (w != preview_width || h != preview_height) - { - preview_width = w; preview_height = h; - preview_frame = traits->make_preview(w, h); - } } } } } -bool Tracker_PT::maybe_reopen_camera() -{ - QMutexLocker l(&camera_mtx); - - return camera->start(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)); + } widget = std::make_unique<video_widget>(video_frame); layout = std::make_unique<QHBoxLayout>(video_frame); layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(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(); + double dpi = screen_dpi(video_frame); + preview_frame = traits->make_preview(iround(preview_width * dpi), + iround(preview_height * dpi)); + start(QThread::HighPriority); return {}; @@ -214,10 +210,10 @@ int Tracker_PT::get_n_points() bool Tracker_PT::get_cam_info(pt_camera_info& info) { - QMutexLocker l(&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; } diff --git a/tracker-pt/ftnoir_tracker_pt.h b/tracker-pt/ftnoir_tracker_pt.h index 210c6a01..a793f94b 100644 --- a/tracker-pt/ftnoir_tracker_pt.h +++ b/tracker-pt/ftnoir_tracker_pt.h @@ -13,13 +13,12 @@ #include "point_tracker.h" #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> @@ -48,14 +47,10 @@ struct Tracker_PT : QThread, ITracker private: void run() override; - - bool maybe_reopen_camera(); - void set_fov(int value); + [[nodiscard]] bool check_camera(); pointer<pt_runtime_traits> traits; - QMutex camera_mtx; - PointTracker point_tracker; pt_settings s; @@ -73,7 +68,10 @@ private: std::atomic<unsigned> point_count { 0 }; std::atomic<bool> ever_success = false; + 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 diff --git a/tracker-pt/ftnoir_tracker_pt_dialog.cpp b/tracker-pt/ftnoir_tracker_pt_dialog.cpp index edf689a9..d67f79a7 100644 --- a/tracker-pt/ftnoir_tracker_pt_dialog.cpp +++ b/tracker-pt/ftnoir_tracker_pt_dialog.cpp @@ -10,8 +10,6 @@ #include "compat/math.hpp" #include "video/camera.hpp" -#include <opencv2/core.hpp> - #include <QString> #include <QtGlobal> #include <QDebug> @@ -39,6 +37,7 @@ TrackerDialog_PT::TrackerDialog_PT(const QString& module_name) : 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); @@ -92,14 +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++) @@ -107,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) @@ -226,14 +253,25 @@ 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()) + { + 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() { QMutexLocker l(&calibrator_mutex); @@ -267,7 +305,7 @@ 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(); } @@ -275,8 +313,18 @@ void TrackerDialog_PT::unregister_tracker() { 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 66981ee6..79cd91bd 100644 --- a/tracker-pt/ftnoir_tracker_pt_dialog.h +++ b/tracker-pt/ftnoir_tracker_pt_dialog.h @@ -26,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(); @@ -36,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); 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'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><html><head/><body><p>For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.</p></body></html></source> + <translation><html><head/><body><p>‚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.</p></body></html></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><html><head/><body><p>Location of the two remaining model points<br/>with respect to the reference point in default pose</p><p>Use any units you want, not necessarily centimeters.</p></body></html></source> + <translation><html><head/><body><p>Ort der verbleibenden zwei Modellpunkte<br/>unter Berücksichtigung der Referenzpunkte der Standard-Pose</p><p>Dies können beliebige Einheiten sein, nicht zwingend Zentimeter.</p></body></html></translation> + </message> + <message> + <source>y:</source> + <translation>y:</translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">3</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">3</span></p></body></html></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">2</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">2</span></p></body></html></translation> + </message> + <message> + <source>Model position</source> + <translation>Modell-Position</translation> + </message> + <message> + <source>Use only yaw and pitch while calibrating. +Don'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><html><head/><body><p><span style=" font-weight:600;">FTNoIR PointTracker Plugin<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Patrick Ruoff</span></p><p><a href="http://ftnoirpt.sourceforge.net/"><span style=" font-weight:600; text-decoration: underline; color:#0000ff;">Manual (external)</span></a></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">FTNoIR PointTracker Plugin<br/>Version 1.1</span></p><p><span style=" font-weight:600;">von Patrick Ruoff</span></p><p><a href="http://ftnoirpt.sourceforge.net/"><span style=" font-weight:600; text-decoration: underline; color:#0000ff;">Anleitung (extern)</span></a></p></body></html></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 '%1'</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 1456157c..fc44b0f1 100644 --- a/tracker-pt/lang/nl_NL.ts +++ b/tracker-pt/lang/nl_NL.ts @@ -108,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> @@ -232,6 +224,94 @@ Don't roll or change position.</source> <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'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><html><head/><body><p>For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.</p></body></html></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> @@ -281,6 +361,13 @@ Don't roll or change position.</source> </message> </context> <context> + <name>pt_impl::Tracker_PT</name> + <message> + <source>Failed to open camera '%1'</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>pt_module::metadata_pt</name> <message> <source>PointTracker 1.1</source> diff --git a/tracker-pt/lang/ru_RU.ts b/tracker-pt/lang/ru_RU.ts index c91dd087..7ff4657e 100644 --- a/tracker-pt/lang/ru_RU.ts +++ b/tracker-pt/lang/ru_RU.ts @@ -112,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> @@ -237,6 +229,94 @@ ROLL или X/Y-смещения.</translation> <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'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><html><head/><body><p>For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.</p></body></html></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> @@ -286,6 +366,13 @@ ROLL или X/Y-смещения.</translation> </message> </context> <context> + <name>pt_impl::Tracker_PT</name> + <message> + <source>Failed to open camera '%1'</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>pt_module::metadata_pt</name> <message> <source>PointTracker 1.1</source> diff --git a/tracker-pt/lang/stub.ts b/tracker-pt/lang/stub.ts index 36090e5e..3dbe208d 100644 --- a/tracker-pt/lang/stub.ts +++ b/tracker-pt/lang/stub.ts @@ -108,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> @@ -232,6 +224,94 @@ Don't roll or change position.</source> <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'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><html><head/><body><p>For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.</p></body></html></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> @@ -281,6 +361,13 @@ Don't roll or change position.</source> </message> </context> <context> + <name>pt_impl::Tracker_PT</name> + <message> + <source>Failed to open camera '%1'</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>pt_module::metadata_pt</name> <message> <source>PointTracker 1.1</source> diff --git a/tracker-pt/lang/zh_CN.ts b/tracker-pt/lang/zh_CN.ts index 667c2811..3519d719 100644 --- a/tracker-pt/lang/zh_CN.ts +++ b/tracker-pt/lang/zh_CN.ts @@ -200,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> @@ -232,6 +224,94 @@ Don't roll or change position.</source> <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'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><html><head/><body><p>For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.</p></body></html></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> @@ -281,6 +361,13 @@ Don't roll or change position.</source> </message> </context> <context> + <name>pt_impl::Tracker_PT</name> + <message> + <source>Failed to open camera '%1'</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>pt_module::metadata_pt</name> <message> <source>PointTracker 1.1</source> diff --git a/tracker-pt/module/CMakeLists.txt b/tracker-pt/module/CMakeLists.txt index f7a6cc7f..b7fc974f 100644 --- a/tracker-pt/module/CMakeLists.txt +++ b/tracker-pt/module/CMakeLists.txt @@ -1,7 +1,10 @@ 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(${self} opentrack-tracker-pt-base) + 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 a70698de..1beba474 100644 --- a/tracker-pt/module/camera.cpp +++ b/tracker-pt/module/camera.cpp @@ -7,10 +7,7 @@ #include "camera.h" #include "frame.hpp" - -#include "compat/math-imports.hpp" - -#include <opencv2/core.hpp> +#include <opencv2/core/mat.hpp> namespace pt_module { @@ -73,23 +70,29 @@ Camera::result Camera::get_frame(pt_frame& frame_) return { false, {} }; } -bool Camera::start(const QString& name, int fps, int res_x, int res_y) +bool Camera::start(const pt_settings& s) { + 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.name != name || (int)cam_desired.fps != fps || cam_desired.res_x != res_x || cam_desired.res_y != res_y || + cam_desired.use_mjpeg != use_mjpeg || !cap || !cap->is_open()) { stop(); cam_desired.name = name; - cam_desired.fps = fps; + 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 = video::make_camera(name); @@ -100,12 +103,16 @@ bool Camera::start(const QString& name, int fps, int res_x, int res_y) 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; 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; @@ -141,7 +148,7 @@ bool Camera::get_frame_(cv::Mat& img) 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, stride); + img = cv::Mat(frame.height, frame.width, CV_8UC(frame.channels), (void*)frame.data, (size_t)stride); return true; } } diff --git a/tracker-pt/module/camera.h b/tracker-pt/module/camera.h index e2ba159f..e4772178 100644 --- a/tracker-pt/module/camera.h +++ b/tracker-pt/module/camera.h @@ -13,8 +13,6 @@ #include <memory> -#include <opencv2/core.hpp> - #include <QString> namespace pt_module { @@ -23,7 +21,7 @@ struct Camera final : pt_camera { Camera(const QString& module_name); - bool start(const QString& name, 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; diff --git a/tracker-pt/module/frame.cpp b/tracker-pt/module/frame.cpp index ab110871..1a276f16 100644 --- a/tracker-pt/module/frame.cpp +++ b/tracker-pt/module/frame.cpp @@ -1,43 +1,52 @@ #include "frame.hpp" - #include "compat/math.hpp" - #include <opencv2/imgproc.hpp> 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; + const bool need_resize = frame.size != frame_copy.size; - if (frame.channels() != 3) + if (frame.channels() == 1) { - eval_once(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); - ensure_size(frame_copy, w, h, CV_8UC3); - - frame_copy.setTo(cv::Scalar(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) { eval_once(qDebug() << "bad stride" << stride << "for bitmap size" << frame_copy.cols << frame_copy.rows); @@ -71,10 +80,4 @@ void Preview::draw_head_center(f x, f y) color, 1); } -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); -} - } // ns pt_module diff --git a/tracker-pt/module/frame.hpp b/tracker-pt/module/frame.hpp index 89334599..0569a323 100644 --- a/tracker-pt/module/frame.hpp +++ b/tracker-pt/module/frame.hpp @@ -2,7 +2,7 @@ #include "pt-api.hpp" -#include <opencv2/core.hpp> +#include <opencv2/core/mat.hpp> #include <QImage> #ifdef __clang__ @@ -24,7 +24,7 @@ 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(f x, f y) override; @@ -32,9 +32,7 @@ struct Preview final : pt_preview operator cv::Mat const&() const { return frame_copy; } private: - static void ensure_size(cv::Mat& frame, int w, int h, int type); - - cv::Mat frame_copy, frame_out; + cv::Mat frame_copy, frame_out, frame_tmp; }; } // ns pt_module 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/zh_CN.ts b/tracker-pt/module/lang/zh_CN.ts index 03d19f4e..c39728a1 100644 --- a/tracker-pt/module/lang/zh_CN.ts +++ b/tracker-pt/module/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>pt_module::metadata_pt</name> <message> diff --git a/tracker-pt/module/point_extractor.cpp b/tracker-pt/module/point_extractor.cpp index 7ad9925a..3329fafc 100644 --- a/tracker-pt/module/point_extractor.cpp +++ b/tracker-pt/module/point_extractor.cpp @@ -9,9 +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/imgproc.hpp> #undef PREVIEW //#define PREVIEW @@ -33,7 +35,7 @@ using namespace numeric_types; /* 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 @@ -42,9 +44,9 @@ 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 vec2 MeanShiftIteration(const cv::Mat1b &frame_gray, const vec2 ¤t_center, f filter_width) @@ -87,21 +89,16 @@ 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 (cv::Mat1b& x : ch) - x = 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_gray_unmasked = 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::Mat1b& dest) @@ -115,8 +112,43 @@ void PointExtractor::extract_single_channel(const cv::Mat& orig_frame, int idx, cv::mixChannels(&orig_frame, 1, &dest, 1, from_to, 1); } +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); + + // 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: @@ -134,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: + { + 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: { - const int W = frame.cols, H = frame.rows, sz = W*H; - cv::reduce(frame.reshape(1, sz), - output.reshape(1, sz), - 1, cv::REDUCE_AVG); + 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: eval_once(qDebug() << "wrong pt_color_type enum value" << int(s.blob_color)); [[fallthrough]]; - case pt_color_natural: + case pt_color_bt709: +do_grayscale: cv::cvtColor(frame, output, cv::COLOR_BGR2GRAY); break; } @@ -177,11 +235,10 @@ void PointExtractor::threshold_image(const cv::Mat& frame_gray, cv::Mat1b& outpu const f radius = threshold_radius_value(frame_gray.cols, frame_gray.rows, threshold_slider_value); float const* const __restrict ptr = hist.ptr<float>(0); - const unsigned area = uround(3 * pi * radius*radius); + const unsigned area = unsigned(iround(3 * pi * radius*radius)); const unsigned sz = unsigned(hist.cols * hist.rows); - constexpr unsigned min_thres = 64; - unsigned thres = min_thres; - for (unsigned i = sz-1, cnt = 0; i > 32; i--) + unsigned thres = 1; + for (unsigned i = sz-1, cnt = 0; i > 1; i--) { cnt += (unsigned)ptr[i]; if (cnt >= area) @@ -209,20 +266,15 @@ static void draw_blobs(cv::Mat& preview_frame, const blob* blobs, unsigned nblob cy = preview_frame.rows / f(size.height), c = std::fmax(f(1), cx+cy)/2; - constexpr unsigned fract_bits = 8; - constexpr int c_fract(1 << fract_bits); - - cv::Point p(iround(b.pos[0] * cx * c_fract), iround(b.pos[1] * cy * c_fract)); + cv::Point p(iround(b.pos[0] * cx), iround(b.pos[1] * cy)); - auto circle_color = k >= PointModel::N_POINTS - ? cv::Scalar(192, 192, 192) - : cv::Scalar(255, 255, 0); + auto outline_color = k >= PointModel::N_POINTS + ? cv::Scalar(192, 192, 192) + : cv::Scalar(255, 255, 0); - const int overlay_size = iround(dpi); - - cv::circle(preview_frame, p, iround((b.radius + f(3.3) * c) * c_fract), - circle_color, overlay_size, - cv::LINE_AA, fract_bits); + 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); @@ -233,25 +285,51 @@ static void draw_blobs(cv::Mat& preview_frame, const blob* blobs, unsigned nblob 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, + cv::FONT_HERSHEY_PLAIN, iround(dpi), text_color, 1); } } -void PointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_frame_, std::vector<vec2>& points) +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; ensure_buffers(frame); - color_to_grayscale(frame, frame_gray_unmasked); + color_to_grayscale(frame, frame_gray); #if defined PREVIEW cv::imshow("capture", frame_gray); cv::waitKey(1); #endif - threshold_image(frame_gray_unmasked, frame_bin); - frame_gray_unmasked.copyTo(frame_gray, frame_bin); + threshold_image(frame_gray, frame_bin); const f region_size_min = (f)s.min_point_size; const f region_size_max = (f)s.max_point_size; @@ -299,7 +377,7 @@ void PointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_ } } - const f radius = std::sqrt(cnt / pi); + const f radius = std::sqrt((f)cnt) / std::sqrt(pi); if (radius > region_size_max || radius < region_size_min) continue; @@ -329,21 +407,14 @@ 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 - + 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/f(2), rect.height/f(2)); // position relative to ROI. + vec2 pos = meanshift_initial_guess(rect, frame_roi); // position relative to ROI. for (int iter = 0; iter < 10; ++iter) { @@ -358,9 +429,10 @@ end: b.pos[1] = pos[1] + rect.y; } - draw_blobs(preview_frame_.as<Frame>()->mat, - blobs.data(), blobs.size(), - frame_gray.size()); + if (preview_visible) + draw_blobs(preview_frame_.as<Frame>()->mat, + blobs.data(), blobs.size(), + frame_gray.size()); // End of mean shift code. At this point, blob positions are updated with hopefully less noisy less biased values. diff --git a/tracker-pt/module/point_extractor.h b/tracker-pt/module/point_extractor.h index a6103667..fbfdbb0b 100644 --- a/tracker-pt/module/point_extractor.h +++ b/tracker-pt/module/point_extractor.h @@ -9,12 +9,10 @@ #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 numeric_types; @@ -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 int max_blobs = 16; pt_settings s; - cv::Mat1b frame_gray_unmasked, frame_bin, frame_gray; + cv::Mat1b frame_bin, frame_gray; cv::Mat1f hist; std::vector<blob> blobs; cv::Mat1b ch[3]; @@ -49,6 +51,7 @@ private: void ensure_buffers(const cv::Mat& frame); 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 e209938f..39e96038 100644 --- a/tracker-pt/point_tracker.cpp +++ b/tracker-pt/point_tracker.cpp @@ -68,7 +68,7 @@ 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 @@ -76,11 +76,10 @@ void PointModel::get_d_order(const vec2* points, unsigned* d_order, const vec2& t d_vals[cnt]; // get sort indices with respect to d scalar product for (unsigned i = 0; i < cnt; ++i) - d_vals[i] = t(d.dot(points[i]), i); + d_vals[i] = {d.dot(points[i]), i}; - std::sort(d_vals, - d_vals + 3, - [](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 < cnt; ++i) d_order[i] = d_vals[i].second; @@ -94,10 +93,11 @@ PointTracker::PointOrder PointTracker::find_correspondences_previous(const vec2* const pt_camera_info& info) { const f 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); + PointTracker::PointOrder p { + project(vec3(0,0,0), fx), + project(model.M01, fx), + project(model.M02, fx) + }; constexpr unsigned sz = PointModel::N_POINTS; @@ -136,7 +136,9 @@ PointTracker::PointOrder PointTracker::find_correspondences_previous(const vec2* 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 f fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y); PointOrder order; @@ -149,7 +151,7 @@ void PointTracker::track(const std::vector<vec2>& points, else order = find_correspondences_previous(points.data(), model, info); - if (POSIT(model, order, fx) != -1) + if (POSIT(model, filter(order, deadzone_amount), fx) != -1) { init_phase = false; t.start(); diff --git a/tracker-pt/point_tracker.h b/tracker-pt/point_tracker.h index 70c7a9fc..7492b4eb 100644 --- a/tracker-pt/point_tracker.h +++ b/tracker-pt/point_tracker.h @@ -11,14 +11,11 @@ #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_impl { @@ -46,7 +43,7 @@ struct PointModel final 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,7 +57,12 @@ 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); + 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); @@ -70,14 +72,13 @@ private: // the points in model order using PointOrder = std::array<vec2, 3>; - 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); // 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 Affine X_CM_expected; - PointOrder prev_positions; Timer t; bool init_phase = true; }; diff --git a/tracker-pt/pt-api.cpp b/tracker-pt/pt-api.cpp index f64d5c9a..d71c6e13 100644 --- a/tracker-pt/pt-api.cpp +++ b/tracker-pt/pt-api.cpp @@ -31,7 +31,7 @@ f pt_point_extractor::threshold_radius_value(int w, int h, int threshold) f cx = w / f{640}, cy = h / f{480}; const f min_radius = f{1.75} * cx; - const f max_radius = f{15} * cy; + const f max_radius = f{30} * cy; const f radius = std::fmax(f{0}, (max_radius-min_radius) * threshold / f(255) + min_radius); @@ -40,15 +40,14 @@ f pt_point_extractor::threshold_radius_value(int w, int h, int threshold) std::tuple<f, f> pt_pixel_pos_mixin::to_pixel_pos(f x, f y, int w, int h) { - return std::make_tuple(w*(x+f{.5}), f{.5}*(h - 2*y*w)); + return { w*(x+f{.5}), f{.5}*(h - 2*y*w) }; } 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 std::make_tuple((px - w/f{2})/w, -(py - h/f{2})/w); + return { (px - w/f{2})/w, -(py - h/f{2})/w }; } 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 741576a1..15021ff3 100644 --- a/tracker-pt/pt-api.hpp +++ b/tracker-pt/pt-api.hpp @@ -6,11 +6,9 @@ #include "options/options.hpp" #include <tuple> -#include <type_traits> +#include <vector> #include <memory> -#include <opencv2/core.hpp> - #include <QImage> #include <QString> @@ -32,6 +30,7 @@ struct pt_camera_info final int res_x = 0; int res_y = 0; QString name; + bool use_mjpeg = false; }; struct pt_pixel_pos_mixin @@ -58,11 +57,21 @@ struct pt_frame : pt_pixel_pos_mixin { 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 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(f x, f y) = 0; }; @@ -75,7 +84,9 @@ struct pt_camera pt_camera(); virtual ~pt_camera(); - [[nodiscard]] virtual bool start(const QString& name, 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 result get_frame(pt_frame& frame) = 0; @@ -87,6 +98,7 @@ struct pt_camera virtual void set_fov(f value) = 0; virtual void show_camera_settings() = 0; + virtual f deadzone_amount() const { return 1; } }; struct pt_point_extractor : pt_pixel_pos_mixin @@ -94,9 +106,11 @@ struct pt_point_extractor : pt_pixel_pos_mixin 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 f threshold_radius_value(int w, int h, int threshold); }; @@ -105,6 +119,8 @@ 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(); diff --git a/tracker-pt/pt-settings.hpp b/tracker-pt/pt-settings.hpp index 723ee08d..5d16d973 100644 --- a/tracker-pt/pt-settings.hpp +++ b/tracker-pt/pt-settings.hpp @@ -8,11 +8,17 @@ 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 { @@ -57,10 +63,18 @@ struct pt_settings final : options::opts 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<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) {} }; diff --git a/tracker-rift-140/CMakeLists.txt b/tracker-rift-140/CMakeLists.txt index 6e54412a..278effe2 100644 --- a/tracker-rift-140/CMakeLists.txt +++ b/tracker-rift-140/CMakeLists.txt @@ -1,5 +1,14 @@ -if(WIN32 OR APPLE) - include(opentrack-rift) - otr_rift(tracker-rift-140 SDK_RIFT_140) +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/lang/zh_CN.ts b/tracker-rift-140/lang/zh_CN.ts index 26ab2040..ff552d19 100644 --- a/tracker-rift-140/lang/zh_CN.ts +++ b/tracker-rift-140/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_rift_140</name> <message> 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 4672b3a4..f48f58d1 100644 --- a/tracker-rs/ftnoir_tracker_rs.cpp +++ b/tracker-rs/ftnoir_tracker_rs.cpp @@ -84,27 +84,16 @@ void RSTracker::handleTrackingEnded(int exitCode){ showRealSenseErrorMessageBox(exitCode); } -bool RSTracker::startSdkInstallationProcess() -{ - bool pStarted = QDesktopServices::openUrl({"https://software.intel.com/en-us/realsense-sdk-windows-eol"}); - 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.")); @@ -116,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 SDK 2016 R2 Runtime"), QMessageBox::ActionRole); msgBox.addButton(QMessageBox::Ok); msgBox.exec(); - - if(msgBox.clickedButton() == triggerSdkInstallation) - startSdkInstallationProcess(); } void RSTracker::data(double *data) diff --git a/tracker-rs/ftnoir_tracker_rs.h b/tracker-rs/ftnoir_tracker_rs.h index f322d0f6..8c540ff8 100644 --- a/tracker-rs/ftnoir_tracker_rs.h +++ b/tracker-rs/ftnoir_tracker_rs.h @@ -24,9 +24,6 @@ public: module_status start_tracker(QFrame *) override; void data(double *data) override; -public slots: - static bool startSdkInstallationProcess(); - protected: void configurePreviewFrame(); diff --git a/tracker-rs/ftnoir_tracker_rs_controls.cpp b/tracker-rs/ftnoir_tracker_rs_controls.cpp index a5018957..230fd9a4 100644 --- a/tracker-rs/ftnoir_tracker_rs_controls.cpp +++ b/tracker-rs/ftnoir_tracker_rs_controls.cpp @@ -14,11 +14,9 @@ RSdialog_realsense::RSdialog_realsense() 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) - 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 de7516b9..7dce262f 100644 --- a/tracker-rs/ftnoir_tracker_rs_controls.ui +++ b/tracker-rs/ftnoir_tracker_rs_controls.ui @@ -6,10 +6,16 @@ <rect> <x>0</x> <y>0</y> - <width>339</width> - <height>155</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> @@ -29,8 +35,32 @@ <layout class="QVBoxLayout" name="verticalLayout"> <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><html><head/><body><p>In order to use this tracker, you need a PC equipped with an Intel® RealSense™ F200 or SR300 camera and the RealSense™ <a href="https://software.intel.com/en-us/realsense-sdk-windows-eol"><span style=" text-decoration: underline; color:#0000ff;">SDK 2016 R2 runtime</span></a>.</p><p>Scroll down to the bottom of the page, choosing <span style=" font-weight:600;">2016 R2 Full SDK</span> on the right-hand side. Unfortunately it's necessary to create an account prior to downloading.</p></body></html></string> + <string><html><head/><body> +In order to use this tracker, you need: + +<ul> +<li> + A PC equipped with an Intel® RealSense™ F200 or SR300 camera +</li> +<li> + Depth Camera Manager <a href="https://www.intel.com/content/www/us/en/download/18309/intel-realsense-depth-camera-manager.html"><span>driver</span></a> + <blockquote> + 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:<br/> + <span style=" font-weight: bold; font-family:monospace, 'Courier New';">--ignore-fw-update --silent --no-progress --acceptlicense=yes</span> + </blockquote> +</li> +<li> + RealSense™ <a href="http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/9078/intel_rs_sdk_offline_package_10.0.26.0396.exe"><span>SDK 2016 R2 runtime</span></a> +</li> +</ul> +</body></html></string> </property> <property name="wordWrap"> <bool>true</bool> @@ -42,6 +72,12 @@ </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/lang/nl_NL.ts b/tracker-rs/lang/nl_NL.ts index 256b7f25..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't initialize RealSense tracking. Please make sure SDK Runtime 2016 R2 is installed.</source> + <source>Couldn'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,10 +15,6 @@ <source>Tracking stopped after another program changed camera streams configuration.</source> <translation type="unfinished"></translation> </message> - <message> - <source>Install SDK 2016 R2 Runtime</source> - <translation type="unfinished"></translation> - </message> </context> <context> <name>RSTrackerMetaData</name> @@ -42,7 +30,25 @@ <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p>In order to use this tracker, you need a PC equipped with an Intel® RealSense™ F200 or SR300 camera and the RealSense™ <a href="https://software.intel.com/en-us/realsense-sdk-windows-eol"><span style=" text-decoration: underline; color:#0000ff;">SDK 2016 R2 runtime</span></a>.</p><p>Scroll down to the bottom of the page, choosing <span style=" font-weight:600;">2016 R2 Full SDK</span> on the right-hand side. Unfortunately it's necessary to create an account prior to downloading.</p></body></html></source> + <source><html><head/><body> +In order to use this tracker, you need: + +<ul> +<li> + A PC equipped with an Intel® RealSense™ F200 or SR300 camera +</li> +<li> + Depth Camera Manager <a href="https://www.intel.com/content/www/us/en/download/18309/intel-realsense-depth-camera-manager.html"><span>driver</span></a> + <blockquote> + 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:<br/> + <span style=" font-weight: bold; font-family:monospace, 'Courier New';">--ignore-fw-update --silent --no-progress --acceptlicense=yes</span> + </blockquote> +</li> +<li> + RealSense™ <a href="http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/9078/intel_rs_sdk_offline_package_10.0.26.0396.exe"><span>SDK 2016 R2 runtime</span></a> +</li> +</ul> +</body></html></source> <translation type="unfinished"></translation> </message> </context> diff --git a/tracker-rs/lang/ru_RU.ts b/tracker-rs/lang/ru_RU.ts index 91668018..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't initialize RealSense tracking. Please make sure SDK Runtime 2016 R2 is installed.</source> + <source>Couldn'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,10 +15,6 @@ <source>Tracking stopped after another program changed camera streams configuration.</source> <translation type="unfinished"></translation> </message> - <message> - <source>Install SDK 2016 R2 Runtime</source> - <translation type="unfinished"></translation> - </message> </context> <context> <name>RSTrackerMetaData</name> @@ -42,7 +30,25 @@ <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p>In order to use this tracker, you need a PC equipped with an Intel® RealSense™ F200 or SR300 camera and the RealSense™ <a href="https://software.intel.com/en-us/realsense-sdk-windows-eol"><span style=" text-decoration: underline; color:#0000ff;">SDK 2016 R2 runtime</span></a>.</p><p>Scroll down to the bottom of the page, choosing <span style=" font-weight:600;">2016 R2 Full SDK</span> on the right-hand side. Unfortunately it's necessary to create an account prior to downloading.</p></body></html></source> + <source><html><head/><body> +In order to use this tracker, you need: + +<ul> +<li> + A PC equipped with an Intel® RealSense™ F200 or SR300 camera +</li> +<li> + Depth Camera Manager <a href="https://www.intel.com/content/www/us/en/download/18309/intel-realsense-depth-camera-manager.html"><span>driver</span></a> + <blockquote> + 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:<br/> + <span style=" font-weight: bold; font-family:monospace, 'Courier New';">--ignore-fw-update --silent --no-progress --acceptlicense=yes</span> + </blockquote> +</li> +<li> + RealSense™ <a href="http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/9078/intel_rs_sdk_offline_package_10.0.26.0396.exe"><span>SDK 2016 R2 runtime</span></a> +</li> +</ul> +</body></html></source> <translation type="unfinished"></translation> </message> </context> diff --git a/tracker-rs/lang/stub.ts b/tracker-rs/lang/stub.ts index 3a8e4f45..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't initialize RealSense tracking. Please make sure SDK Runtime 2016 R2 is installed.</source> + <source>Couldn'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,10 +15,6 @@ <source>Tracking stopped after another program changed camera streams configuration.</source> <translation type="unfinished"></translation> </message> - <message> - <source>Install SDK 2016 R2 Runtime</source> - <translation type="unfinished"></translation> - </message> </context> <context> <name>RSTrackerMetaData</name> @@ -42,7 +30,25 @@ <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p>In order to use this tracker, you need a PC equipped with an Intel® RealSense™ F200 or SR300 camera and the RealSense™ <a href="https://software.intel.com/en-us/realsense-sdk-windows-eol"><span style=" text-decoration: underline; color:#0000ff;">SDK 2016 R2 runtime</span></a>.</p><p>Scroll down to the bottom of the page, choosing <span style=" font-weight:600;">2016 R2 Full SDK</span> on the right-hand side. Unfortunately it's necessary to create an account prior to downloading.</p></body></html></source> + <source><html><head/><body> +In order to use this tracker, you need: + +<ul> +<li> + A PC equipped with an Intel® RealSense™ F200 or SR300 camera +</li> +<li> + Depth Camera Manager <a href="https://www.intel.com/content/www/us/en/download/18309/intel-realsense-depth-camera-manager.html"><span>driver</span></a> + <blockquote> + 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:<br/> + <span style=" font-weight: bold; font-family:monospace, 'Courier New';">--ignore-fw-update --silent --no-progress --acceptlicense=yes</span> + </blockquote> +</li> +<li> + RealSense™ <a href="http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/9078/intel_rs_sdk_offline_package_10.0.26.0396.exe"><span>SDK 2016 R2 runtime</span></a> +</li> +</ul> +</body></html></source> <translation type="unfinished"></translation> </message> </context> diff --git a/tracker-rs/lang/zh_CN.ts b/tracker-rs/lang/zh_CN.ts index 3a8e4f45..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't initialize RealSense tracking. Please make sure SDK Runtime 2016 R2 is installed.</source> + <source>Couldn'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,10 +15,6 @@ <source>Tracking stopped after another program changed camera streams configuration.</source> <translation type="unfinished"></translation> </message> - <message> - <source>Install SDK 2016 R2 Runtime</source> - <translation type="unfinished"></translation> - </message> </context> <context> <name>RSTrackerMetaData</name> @@ -42,7 +30,25 @@ <translation type="unfinished"></translation> </message> <message> - <source><html><head/><body><p>In order to use this tracker, you need a PC equipped with an Intel® RealSense™ F200 or SR300 camera and the RealSense™ <a href="https://software.intel.com/en-us/realsense-sdk-windows-eol"><span style=" text-decoration: underline; color:#0000ff;">SDK 2016 R2 runtime</span></a>.</p><p>Scroll down to the bottom of the page, choosing <span style=" font-weight:600;">2016 R2 Full SDK</span> on the right-hand side. Unfortunately it's necessary to create an account prior to downloading.</p></body></html></source> + <source><html><head/><body> +In order to use this tracker, you need: + +<ul> +<li> + A PC equipped with an Intel® RealSense™ F200 or SR300 camera +</li> +<li> + Depth Camera Manager <a href="https://www.intel.com/content/www/us/en/download/18309/intel-realsense-depth-camera-manager.html"><span>driver</span></a> + <blockquote> + 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:<br/> + <span style=" font-weight: bold; font-family:monospace, 'Courier New';">--ignore-fw-update --silent --no-progress --acceptlicense=yes</span> + </blockquote> +</li> +<li> + RealSense™ <a href="http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/9078/intel_rs_sdk_offline_package_10.0.26.0396.exe"><span>SDK 2016 R2 runtime</span></a> +</li> +</ul> +</body></html></source> <translation type="unfinished"></translation> </message> </context> diff --git a/tracker-rs/rs_impl/ftnoir_tracker_rs_impl.cpp b/tracker-rs/rs_impl/ftnoir_tracker_rs_impl.cpp index 3230ef56..f54531a6 100644 --- a/tracker-rs/rs_impl/ftnoir_tracker_rs_impl.cpp +++ b/tracker-rs/rs_impl/ftnoir_tracker_rs_impl.cpp @@ -9,15 +9,21 @@ #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> +#endif -#include <cstdlib> - -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-s2bot/ftnoir_tracker_s2bot.cpp b/tracker-s2bot/ftnoir_tracker_s2bot.cpp index c1cb7d9b..98a299ca 100644 --- a/tracker-s2bot/ftnoir_tracker_s2bot.cpp +++ b/tracker-s2bot/ftnoir_tracker_s2bot.cpp @@ -27,6 +27,10 @@ static constexpr int add_cbx[] = -180, }; +#ifdef __GNUG__ +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + void tracker_s2bot::run() { int freq = s.freq; if (freq <= 0) @@ -34,7 +38,10 @@ void tracker_s2bot::run() { timer.setInterval((int)(1000./freq)); timer.setSingleShot(false); connect(&timer, &QTimer::timeout, [this] { - auto reply = m_nam->get(QNetworkRequest(QUrl("http://localhost:17317/poll"))); + 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"; @@ -50,9 +57,9 @@ 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, }; 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/zh_CN.ts b/tracker-s2bot/lang/zh_CN.ts index babca884..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,47 +37,47 @@ </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 type="unfinished"></translation> + <translation>滚转</translation> </message> </context> <context> diff --git a/tracker-steamvr/CMakeLists.txt b/tracker-steamvr/CMakeLists.txt index dc487cba..eb8b20b9 100644 --- a/tracker-steamvr/CMakeLists.txt +++ b/tracker-steamvr/CMakeLists.txt @@ -38,7 +38,7 @@ if(steamvr-dll AND opentrack-intel) 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 PRIVATE "${SDK_VALVE_STEAMVR}/headers") target_link_libraries(opentrack-tracker-steamvr "${SDK_VALVE_STEAMVR}/lib/${steamvr-dir}/${steamvr-lib}") diff --git a/tracker-steamvr/lang/zh_CN.ts b/tracker-steamvr/lang/zh_CN.ts index 7b68034b..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> diff --git a/tracker-steamvr/steamvr.cpp b/tracker-steamvr/steamvr.cpp index 3bcfcc25..05b5ed35 100644 --- a/tracker-steamvr/steamvr.cpp +++ b/tracker-steamvr/steamvr.cpp @@ -89,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; } @@ -216,9 +221,17 @@ module_status steamvr::start_tracker(QFrame*) 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); }); } @@ -253,12 +266,19 @@ bool steamvr::center() { 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 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 "Abort" 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/zh_CN.ts b/tracker-test/lang/zh_CN.ts index 78f647f5..5df28f2f 100644 --- a/tracker-test/lang/zh_CN.ts +++ b/tracker-test/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>test_metadata</name> <message> diff --git a/tracker-test/test.h b/tracker-test/test.h index 7f06968e..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> diff --git a/tracker-tobii/CMakeLists.txt b/tracker-tobii/CMakeLists.txt index 8551c8fa..8273d43e 100644 --- a/tracker-tobii/CMakeLists.txt +++ b/tracker-tobii/CMakeLists.txt @@ -1,17 +1,22 @@ -# https://developer.tobii.com/download-packages/stream-engine-4-1-0-for-windows-x86 -# https://developer.tobii.com/download-packages/stream-engine-4-1-0-for-windows-x64 -set(SDK_TOBII "" CACHE PATH "Tobii SDK path") -if(SDK_TOBII) +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) - target_include_directories(${self} SYSTEM PRIVATE "${SDK_TOBII}/include") - set(dll "${SDK_TOBII}/lib/tobii/tobii_stream_engine.dll") - set(lib "${SDK_TOBII}/lib/tobii/tobii_stream_engine.lib") + 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}") - message(${self}) - message(${dll}) - message(${lib}) + set(dll "${SDK_TOBII}/lib/${arch}/tobii_stream_engine.dll") + set(lib tobii_stream_engine.lib) - install(FILES ${dll} DESTINATION ${opentrack-hier-pfx}) target_link_libraries(${self} ${lib}) + install(FILES ${dll} DESTINATION ${opentrack-libexec}) endif() diff --git a/tracker-tobii/ftnoir_tracker_tobii.cpp b/tracker-tobii/ftnoir_tracker_tobii.cpp deleted file mode 100644 index 43cb1662..00000000 --- a/tracker-tobii/ftnoir_tracker_tobii.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "ftnoir_tracker_tobii.h" -#include "api/plugin-api.hpp" -#include "compat/math.hpp" - -tobii::~tobii() = default; - -module_status tobii::start_tracker(QFrame*) -{ - t.start(); - return status_ok(); -} - -void tobii::data(double *data) -{ - if (t.head_pose) { - tobii_head_pose_t p = *t.head_pose; - if (p.position_validity == TOBII_VALIDITY_VALID) { - if (center_pose.position_validity == TOBII_VALIDITY_VALID) { - p.position_xyz[0] = p.position_xyz[0] - center_pose.position_xyz[0]; - p.position_xyz[1] = p.position_xyz[1] - center_pose.position_xyz[1]; - p.position_xyz[2] = p.position_xyz[2] - center_pose.position_xyz[2]; - } - else { - center_pose = p; - } - data[0] = clamp(p.position_xyz[0] * 30.0 / 300.0, -30.0, 30.0); - data[1] = clamp(p.position_xyz[1] * 30.0 / 300.0, -30.0, 30.0); - data[2] = clamp(p.position_xyz[2] * 30.0 / 300.0, -30.0, 30.0); - } - - double max_yaw = 90.0; - if (p.rotation_validity_xyz[1] == TOBII_VALIDITY_VALID) { - if (center_pose.rotation_validity_xyz[1] == TOBII_VALIDITY_VALID) { - p.rotation_xyz[1] = p.rotation_xyz[1] - center_pose.rotation_xyz[1]; - } - data[3] = clamp(p.rotation_xyz[1] * 100.0 * max_yaw / 90.0, -max_yaw, max_yaw); - } - - double max_pitch = 90.0; - if (p.rotation_validity_xyz[0] == TOBII_VALIDITY_VALID) { - if (center_pose.rotation_validity_xyz[0] == TOBII_VALIDITY_VALID) { - p.rotation_xyz[0] = p.rotation_xyz[0] - center_pose.rotation_xyz[0]; - } - data[4] = clamp(p.rotation_xyz[0] * 100.0 * max_pitch / 90.0, -max_pitch, max_pitch); - } - - double max_roll = 90.0; - if (p.rotation_validity_xyz[2] == TOBII_VALIDITY_VALID) { - if (center_pose.rotation_validity_xyz[2] == TOBII_VALIDITY_VALID) { - p.rotation_xyz[2] = p.rotation_xyz[2] - center_pose.rotation_xyz[2]; - } - data[5] = clamp(p.rotation_xyz[2] * 100.0 * max_roll / 90.0, -max_roll, max_roll); - } - } -} - -bool tobii::center () -{ - if (t.head_pose) { - center_pose = *t.head_pose; - } - return false; -} - -OPENTRACK_DECLARE_TRACKER(tobii, dialog_tobii, tobiiDll) diff --git a/tracker-tobii/ftnoir_tracker_tobii.h b/tracker-tobii/ftnoir_tracker_tobii.h deleted file mode 100644 index bd7a04a2..00000000 --- a/tracker-tobii/ftnoir_tracker_tobii.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once -#include "ui_ftnoir_tracker_tobii_controls.h" -#include <QComboBox> -#include <QCheckBox> -#include <QSpinBox> -#include <QMessageBox> -#include <QSettings> -#include <QList> -#include <QFrame> -#include <QStringList> -#include <cmath> -#include "api/plugin-api.hpp" -#include "options/options.hpp" - -#include "thread.hpp" - -class tobii : public ITracker -{ -public: - ~tobii(); - module_status start_tracker(QFrame*) override; - void data(double* data) override; - virtual bool center() override; -private: - tobii_thread t; - tobii_head_pose_t center_pose; -}; - -class dialog_tobii: public ITrackerDialog -{ - Q_OBJECT -public: - dialog_tobii(); - ~dialog_tobii() = default; - void register_tracker(ITracker *) {} - void unregister_tracker() {} - Ui::UITobiiControls ui; - tobii* tracker; -private slots: - void doOK(); - void doCancel(); -}; - -class tobiiDll : public Metadata -{ - Q_OBJECT - - QString name() { return tr("Tobii input"); } - QIcon icon() { return QIcon(":/images/opentrack.png"); } -}; diff --git a/tracker-tobii/ftnoir_tracker_tobii_controls.ui b/tracker-tobii/ftnoir_tracker_tobii_controls.ui deleted file mode 100644 index 6cd00121..00000000 --- a/tracker-tobii/ftnoir_tracker_tobii_controls.ui +++ /dev/null @@ -1,56 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>UITobiiControls</class> - <widget class="QWidget" name="UITobiiControls"> - <property name="windowModality"> - <enum>Qt::NonModal</enum> - </property> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>498</width> - <height>303</height> - </rect> - </property> - <property name="windowTitle"> - <string>Tobii settings</string> - </property> - <property name="windowIcon"> - <iconset> - <normaloff>../gui/images/opentrack.png</normaloff>../gui/images/opentrack.png - </iconset> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <property name="leftMargin"> - <number>12</number> - </property> - <property name="topMargin"> - <number>6</number> - </property> - <property name="rightMargin"> - <number>12</number> - </property> - <property name="bottomMargin"> - <number>6</number> - </property> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> - </layout> - </widget> - <tabstops> - <tabstop>buttonBox</tabstop> - </tabstops> - <resources/> - <connections/> - <slots> - <slot>startEngineClicked()</slot> - <slot>stopEngineClicked()</slot> - <slot>cameraSettingsClicked()</slot> - </slots> -</ui> diff --git a/tracker-tobii/ftnoir_tracker_tobii_dialog.cpp b/tracker-tobii/ftnoir_tracker_tobii_dialog.cpp deleted file mode 100644 index 5cd4bad0..00000000 --- a/tracker-tobii/ftnoir_tracker_tobii_dialog.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "ftnoir_tracker_tobii.h" -#include "api/plugin-api.hpp" - -dialog_tobii::dialog_tobii() : tracker(nullptr) -{ - ui.setupUi( this ); - - // Connect Qt signals to member-functions - connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK())); - connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel())); -} - -void dialog_tobii::doOK() { - close(); -} - -void dialog_tobii::doCancel() { - close(); -} diff --git a/tracker-tobii/lang/nl_NL.ts b/tracker-tobii/lang/nl_NL.ts index 45e4974f..538a31f1 100644 --- a/tracker-tobii/lang/nl_NL.ts +++ b/tracker-tobii/lang/nl_NL.ts @@ -2,16 +2,20 @@ <!DOCTYPE TS> <TS version="2.1" language="nl_NL"> <context> - <name>UITobiiControls</name> + <name>tobii_metadata</name> <message> - <source>Tobii settings</source> + <source>Tobii Eye Tracker</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>tobiiDll</name> + <name>tobii_ui</name> <message> - <source>Tobii input</source> + <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> diff --git a/tracker-tobii/lang/ru_RU.ts b/tracker-tobii/lang/ru_RU.ts index ee73b375..b86ba010 100644 --- a/tracker-tobii/lang/ru_RU.ts +++ b/tracker-tobii/lang/ru_RU.ts @@ -2,16 +2,20 @@ <!DOCTYPE TS> <TS version="2.1" language="ru_RU"> <context> - <name>UITobiiControls</name> + <name>tobii_metadata</name> <message> - <source>Tobii settings</source> + <source>Tobii Eye Tracker</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>tobiiDll</name> + <name>tobii_ui</name> <message> - <source>Tobii input</source> + <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> diff --git a/tracker-tobii/lang/stub.ts b/tracker-tobii/lang/stub.ts index cd8a2bdc..566900a3 100644 --- a/tracker-tobii/lang/stub.ts +++ b/tracker-tobii/lang/stub.ts @@ -2,16 +2,20 @@ <!DOCTYPE TS> <TS version="2.1"> <context> - <name>UITobiiControls</name> + <name>tobii_metadata</name> <message> - <source>Tobii settings</source> + <source>Tobii Eye Tracker</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>tobiiDll</name> + <name>tobii_ui</name> <message> - <source>Tobii input</source> + <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> diff --git a/tracker-tobii/lang/zh_CN.ts b/tracker-tobii/lang/zh_CN.ts index cd8a2bdc..908770e3 100644 --- a/tracker-tobii/lang/zh_CN.ts +++ b/tracker-tobii/lang/zh_CN.ts @@ -1,18 +1,22 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<TS version="2.1" language="zh_CN"> <context> - <name>UITobiiControls</name> + <name>tobii_metadata</name> <message> - <source>Tobii settings</source> + <source>Tobii Eye Tracker</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>tobiiDll</name> + <name>tobii_ui</name> <message> - <source>Tobii input</source> + <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/thread.cpp b/tracker-tobii/thread.cpp deleted file mode 100644 index bdd48125..00000000 --- a/tracker-tobii/thread.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include "thread.hpp" -#include "compat/sleep.hpp" - -tobii_thread::~tobii_thread() -{ - exit_thread = true; - wait(); - - if (device) tobii_device_destroy(device); - if (api) tobii_api_destroy(api); - - quit(); -} - -void tobii_thread::run() -{ - /* See https://developer.tobii.com/consumer-eye-trackers/stream-engine/ */ - if (tobii_api_create(&api, nullptr, nullptr) != TOBII_ERROR_NO_ERROR) - { - error_last = "Failed to initialize the Tobii Stream Engine API."; - exit_thread = true; - } - - std::vector<std::string> devices; - if (tobii_enumerate_local_device_urls(api, - [](char const* url, void* user_data) - { - auto list = (std::vector<std::string>*) user_data; - list->push_back(url); - }, &devices) != TOBII_ERROR_NO_ERROR) - { - error_last = "Failed to enumerate devices."; - exit_thread = true; - } - - if (devices.size() == 0) - { - tobii_api_destroy(api); - error_last = "No stream engine compatible device(s) found."; - exit_thread = true; - } - std::string selected_device = devices[0]; - - unsigned int retry = 0; - tobii_error_t tobii_error = TOBII_ERROR_NO_ERROR; - do - { - tobii_error = tobii_device_create(api, selected_device.c_str(), TOBII_FIELD_OF_USE_INTERACTIVE, &device); - if ((tobii_error != TOBII_ERROR_CONNECTION_FAILED) && (tobii_error != TOBII_ERROR_FIRMWARE_UPGRADE_IN_PROGRESS)) break; - portable::sleep(interval); - ++retry; - } while (retry < retries); - if (tobii_error != TOBII_ERROR_NO_ERROR) { - tobii_api_destroy(api); - error_last = "Failed to connect to device."; - exit_thread = true; - } - - if (tobii_head_pose_subscribe(device, [](tobii_head_pose_t const* head_pose, void* user_data) { - - if ((*head_pose).position_validity != TOBII_VALIDITY_VALID - || (*head_pose).rotation_validity_xyz[0] != TOBII_VALIDITY_VALID - || (*head_pose).rotation_validity_xyz[1] != TOBII_VALIDITY_VALID - || (*head_pose).rotation_validity_xyz[2] != TOBII_VALIDITY_VALID) return; - - tobii_head_pose_t* tobii_head_pose_storage = (tobii_head_pose_t*)user_data; - *tobii_head_pose_storage = *head_pose; - - }, head_pose) != TOBII_ERROR_NO_ERROR) - { - error_last = "Failed to subscribe to head pose stream."; - exit_thread = true; - } - - tobii_error = TOBII_ERROR_NO_ERROR; - while (!exit_thread) - { - tobii_error = tobii_wait_for_callbacks(1, &device); - if (tobii_error == TOBII_ERROR_TIMED_OUT) continue; - else if (tobii_error != TOBII_ERROR_NO_ERROR) - { - error_last = "tobii_wait_for_callbacks failed."; - } - - tobii_error = tobii_device_process_callbacks(device); - if (tobii_error == TOBII_ERROR_CONNECTION_FAILED) - { - unsigned int retry = 0; - auto tobii_error = TOBII_ERROR_NO_ERROR; - do - { - tobii_error = tobii_device_reconnect(device); - if ((tobii_error != TOBII_ERROR_CONNECTION_FAILED) && (tobii_error != TOBII_ERROR_FIRMWARE_UPGRADE_IN_PROGRESS)) break; - portable::sleep(interval); - ++retry; - } while (retry < retries); - if (tobii_error != TOBII_ERROR_NO_ERROR) - { - error_last = "Connection was lost and reconnection failed."; - exit_thread = true; - } - continue; - } - else if (tobii_error != TOBII_ERROR_NO_ERROR) - { - error_last = "tobii_device_process_callbacks failed."; - } - } -} diff --git a/tracker-tobii/thread.hpp b/tracker-tobii/thread.hpp deleted file mode 100644 index d311db87..00000000 --- a/tracker-tobii/thread.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include <QThread> -#include <QCoreApplication> - -#include <tobii/tobii.h> -#include <tobii/tobii_streams.h> -#include <atomic> -#include <vector> -#include <string> - -class tobii_thread : public QThread -{ - Q_OBJECT - void run() override; - -public: - tobii_thread() - { - head_pose = new tobii_head_pose_t(); - } - ~tobii_thread() override; - - tobii_head_pose_t* head_pose; - -private: - tobii_api_t* api; - tobii_device_t* device; - - static constexpr unsigned int retries = 300; - static constexpr unsigned int interval = 100; - - QString error_last = ""; - std::atomic<bool> exit_thread = false; -}; diff --git a/tracker-tobii/tobii.cpp b/tracker-tobii/tobii.cpp new file mode 100644 index 00000000..8cf59335 --- /dev/null +++ b/tracker-tobii/tobii.cpp @@ -0,0 +1,121 @@ +/* Copyright (c) 2023, Khoa Nguyen <khoanguyen@3forcom.com> + + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted, + * provided that the above copyright notice and this permission + * notice appear in all copies. + */ + +#include "tobii.h" +#include "compat/math-imports.hpp" + +#include <QMutexLocker> + +static constexpr double rad_to_deg = 180.0 * M_1_PI; +static constexpr double mm_to_cm = 0.1; + +static void url_receiver(char const* url, void* user_data) +{ + char* buffer = (char*)user_data; + if (*buffer != '\0') + return; // only keep first value + + if (strlen(url) < 256) + strcpy(buffer, url); +} + +static void head_pose_callback(tobii_head_pose_t const* head_pose, void* user_data) +{ + // Store the latest head pose data in the supplied storage + tobii_head_pose_t* head_pose_storage = (tobii_head_pose_t*)user_data; + *head_pose_storage = *head_pose; +} + +tobii_tracker::tobii_tracker() = default; + +tobii_tracker::~tobii_tracker() +{ + QMutexLocker lck(&mtx); + if (device) + { + tobii_head_pose_unsubscribe(device); + tobii_device_destroy(device); + } + if (api) + { + tobii_api_destroy(api); + } +} + +module_status tobii_tracker::start_tracker(QFrame*) +{ + QMutexLocker lck(&mtx); + tobii_error_t tobii_error = tobii_api_create(&api, nullptr, nullptr); + if (tobii_error != TOBII_ERROR_NO_ERROR) + { + return error("Failed to initialize the Tobii Stream Engine API."); + } + + char url[256] = { 0 }; + tobii_error = tobii_enumerate_local_device_urls(api, url_receiver, url); + if (tobii_error != TOBII_ERROR_NO_ERROR || url[0] == '\0') + { + tobii_api_destroy(api); + return error("No stream engine compatible device(s) found."); + } + + tobii_error = tobii_device_create(api, url, &device); + if (tobii_error != TOBII_ERROR_NO_ERROR) + { + tobii_api_destroy(api); + return error(QString("Failed to connect to %1.").arg(url)); + } + + tobii_error = tobii_head_pose_subscribe(device, head_pose_callback, &latest_head_pose); + if (tobii_error != TOBII_ERROR_NO_ERROR) + { + tobii_device_destroy(device); + tobii_api_destroy(api); + return error("Failed to subscribe to head pose stream."); + } + + return status_ok(); +} + +void tobii_tracker::data(double* data) +{ + QMutexLocker lck(&mtx); + tobii_error_t tobii_error = tobii_device_process_callbacks(device); + if (tobii_error != TOBII_ERROR_NO_ERROR) + { + return; + } + + // Tobii coordinate system is different from OpenTrack's + // Tobii: +x is to the right, +y is up, +z is towards the user + // Rotation xyz is in radians, x is pitch, y is yaw, z is roll + + if (latest_head_pose.position_validity == TOBII_VALIDITY_VALID) + { + data[TX] = -latest_head_pose.position_xyz[0] * mm_to_cm; + data[TY] = latest_head_pose.position_xyz[1] * mm_to_cm; + data[TZ] = latest_head_pose.position_xyz[2] * mm_to_cm; + } + + if (latest_head_pose.rotation_validity_xyz[0] == TOBII_VALIDITY_VALID) + { + data[Pitch] = latest_head_pose.rotation_xyz[0] * rad_to_deg; + } + + if (latest_head_pose.rotation_validity_xyz[1] == TOBII_VALIDITY_VALID) + { + data[Yaw] = -latest_head_pose.rotation_xyz[1] * rad_to_deg; + } + + if (latest_head_pose.rotation_validity_xyz[2] == TOBII_VALIDITY_VALID) + { + data[Roll] = latest_head_pose.rotation_xyz[2] * rad_to_deg; + } +} + +OPENTRACK_DECLARE_TRACKER(tobii_tracker, tobii_dialog, tobii_metadata) diff --git a/tracker-tobii/tobii.h b/tracker-tobii/tobii.h new file mode 100644 index 00000000..414f9f64 --- /dev/null +++ b/tracker-tobii/tobii.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2023, Khoa Nguyen <khoanguyen@3forcom.com> + + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted, + * provided that the above copyright notice and this permission + * notice appear in all copies. + */ + +#pragma once +#include "api/plugin-api.hpp" +#include "ui_tobii.h" + +#include <tobii/tobii.h> +#include <tobii/tobii_streams.h> + +#include <QMutex> + +class tobii_tracker : public ITracker +{ +public: + tobii_tracker(); + ~tobii_tracker() override; + module_status start_tracker(QFrame*) override; + void data(double* data) override; + +private: + tobii_api_t* api = nullptr; + tobii_device_t* device = nullptr; + + tobii_head_pose_t latest_head_pose{ + .timestamp_us = 0LL, + .position_validity = TOBII_VALIDITY_INVALID, + .position_xyz = { 0.f, 0.f, 0.f }, + .rotation_validity_xyz = { TOBII_VALIDITY_INVALID, TOBII_VALIDITY_INVALID, TOBII_VALIDITY_INVALID }, + .rotation_xyz = { 0.f, 0.f, 0.f }, + }; + + QMutex mtx; +}; + +class tobii_dialog : public ITrackerDialog +{ + Q_OBJECT + + Ui::tobii_ui ui; + +public: + tobii_dialog(); + +private slots: + void doOK(); + void doCancel(); +}; + +class tobii_metadata : public Metadata +{ + Q_OBJECT + + QString name() override { return tr("Tobii Eye Tracker"); } + QIcon icon() override { return QIcon(":/images/tobii_logo.png"); } +}; diff --git a/tracker-tobii/tobii.qrc b/tracker-tobii/tobii.qrc new file mode 100644 index 00000000..3e785ef7 --- /dev/null +++ b/tracker-tobii/tobii.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/images"> + <file>tobii_logo.png</file> + </qresource> +</RCC> diff --git a/tracker-tobii/tobii.ui b/tracker-tobii/tobii.ui new file mode 100644 index 00000000..71b29c12 --- /dev/null +++ b/tracker-tobii/tobii.ui @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>tobii_ui</class> + <widget class="QWidget" name="tobii_ui"> + <property name="windowModality"> + <enum>Qt::NonModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>278</width> + <height>58</height> + </rect> + </property> + <property name="windowTitle"> + <string>Tobii Eye Tracker</string> + </property> + <property name="windowIcon"> + <iconset> + <normaloff>:/images/tobii_logo.png</normaloff>:/images/tobii_logo.png</iconset> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Please make sure the Tobii Experience application is running and tracking is active.</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="tobii.qrc"/> + </resources> + <connections/> + <slots> + <slot>startEngineClicked()</slot> + <slot>stopEngineClicked()</slot> + <slot>cameraSettingsClicked()</slot> + </slots> +</ui> diff --git a/tracker-tobii/tobii_dialog.cpp b/tracker-tobii/tobii_dialog.cpp new file mode 100644 index 00000000..689cae38 --- /dev/null +++ b/tracker-tobii/tobii_dialog.cpp @@ -0,0 +1,20 @@ +#include "tobii.h" + +tobii_dialog::tobii_dialog() // NOLINT(cppcoreguidelines-pro-type-member-init) +{ + ui.setupUi(this); + + connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK())); + connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel())); +} + +void tobii_dialog::doOK() +{ + // s.b->save(); + close(); +} + +void tobii_dialog::doCancel() +{ + close(); +} diff --git a/tracker-tobii/tobii_logo.png b/tracker-tobii/tobii_logo.png Binary files differnew file mode 100644 index 00000000..3502b37b --- /dev/null +++ b/tracker-tobii/tobii_logo.png 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 Binary files differnew file mode 100644 index 00000000..9e856c23 --- /dev/null +++ b/tracker-trackhat/images/trackhat-64x64.png diff --git a/tracker-trackhat/images/trackhat.ico b/tracker-trackhat/images/trackhat.ico Binary files differnew file mode 100644 index 00000000..b5f34db3 --- /dev/null +++ b/tracker-trackhat/images/trackhat.ico diff --git a/tracker-trackhat/images/trackhat.png b/tracker-trackhat/images/trackhat.png Binary files differnew file mode 100644 index 00000000..4f17de81 --- /dev/null +++ b/tracker-trackhat/images/trackhat.png 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, ®s)); + 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 6badaa0f..f1f9f033 100644 --- a/tracker-udp/ftnoir_tracker_udp.cpp +++ b/tracker-udp/ftnoir_tracker_udp.cpp @@ -70,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); 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'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/zh_CN.ts b/tracker-udp/lang/zh_CN.ts index 0ebbf95f..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> diff --git a/tracker-wii/CMakeLists.txt b/tracker-wii/CMakeLists.txt index 00cc2f9f..fb840385 100644 --- a/tracker-wii/CMakeLists.txt +++ b/tracker-wii/CMakeLists.txt @@ -2,9 +2,12 @@ if(WIN32) 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(${self} opentrack-tracker-pt-base opentrack-wiiyourself bthprops) + target_link_libraries(${self} opencv_imgproc opentrack-tracker-pt-base opentrack-wiiyourself bthprops) if(MINGW32) target_link_libraries(${self} hid) endif() diff --git a/tracker-wii/lang/zh_CN.ts b/tracker-wii/lang/zh_CN.ts index d67c57ad..6959d95b 100644 --- a/tracker-wii/lang/zh_CN.ts +++ b/tracker-wii/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>wii_metadata_pt</name> <message> diff --git a/tracker-wii/wii_camera.cpp b/tracker-wii/wii_camera.cpp index 973d7fab..bec47651 100644 --- a/tracker-wii/wii_camera.cpp +++ b/tracker-wii/wii_camera.cpp @@ -15,11 +15,9 @@ #include "wii_camera.h" #include "wii_frame.hpp" -#include "compat/sleep.hpp" #include "compat/math-imports.hpp" #include <opencv2/imgproc.hpp> - #include <bluetoothapis.h> using namespace pt_module; @@ -35,83 +33,80 @@ WIICamera::WIICamera(const QString& module_name) : s { module_name } WIICamera::~WIICamera() { - // XXX why is this commented out? -sh 20190414 - //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; + default: + break; } return result(true, cam_info); } -bool WIICamera::start(const QString&, int, int, int) +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() { - desired_name = QString(); - active_name = QString(); - cam_info = pt_camera_info(); - cam_desired = pt_camera_info(); - onExit = true; - - if (m_pDev) - { - m_pDev->ChangedCallback = nullptr; - m_pDev->Disconnect(); - m_pDev = nullptr; - } - - Beep(1000, 200); + if (!m_pDev) + return; + + cam_info = {}; + cam_desired = {}; + pitch_ = 0; roll_ = 0; + + m_pDev->ChangedCallback = nullptr; + m_pDev->Disconnect(); + m_pDev = nullptr; } #ifdef __MINGW32__ @@ -149,7 +144,6 @@ wii_camera_status WIICamera::pair() while (ibtidx < max_devices && BluetoothFindNextRadio(&bt_param, hbtlist + ibtidx)); BluetoothFindRadioClose(hbt); - int i; bool error = false; for (i = 0; i < ibtidx; i++) diff --git a/tracker-wii/wii_camera.h b/tracker-wii/wii_camera.h index 3d0ad1aa..3a7993aa 100644 --- a/tracker-wii/wii_camera.h +++ b/tracker-wii/wii_camera.h @@ -27,47 +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(const QString& name, 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(f x) override { (void) x; } - 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; 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); - double dt_mean = 0; + pt_camera_info cam_info; + pt_camera_info cam_desired; + int pitch_ = 0, roll_ = 0; + pt_settings s; - Timer t; - - pt_camera_info cam_info; - pt_camera_info cam_desired; - QString desired_name, active_name; - - pt_settings s; - - int pitch_ = 0, roll_ = 0; - - 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 8f18f4b8..fcde5235 100644 --- a/tracker-wii/wii_frame.cpp +++ b/tracker-wii/wii_frame.cpp @@ -17,7 +17,7 @@ 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; @@ -25,18 +25,13 @@ WIIPreview& WIIPreview::operator=(const pt_frame& frame_) status = wii.status; if (frame.channels() != 3) - { eval_once(qDebug() << "tracker/pt: camera frame depth: 3 !=" << frame.channels()); - return *this; - } 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) diff --git a/tracker-wii/wii_frame.hpp b/tracker-wii/wii_frame.hpp index 5d6dd199..bbb0c469 100644 --- a/tracker-wii/wii_frame.hpp +++ b/tracker-wii/wii_frame.hpp @@ -47,7 +47,7 @@ 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(f x, f y) override; @@ -58,7 +58,7 @@ private: static void ensure_size(cv::Mat& frame, int w, int h, int type); cv::Mat frame_copy, frame_out; - wii_camera_status status; + wii_camera_status status = wii_cam_wait_for_connect; }; } // ns pt_module diff --git a/tracker-wii/wii_point_extractor.cpp b/tracker-wii/wii_point_extractor.cpp index 393dc1c9..4f1f92b9 100644 --- a/tracker-wii/wii_point_extractor.cpp +++ b/tracker-wii/wii_point_extractor.cpp @@ -27,7 +27,6 @@ using namespace pt_module; WIIPointExtractor::WIIPointExtractor(const QString& module_name) : s(module_name) { - } //define a temp draw function @@ -35,8 +34,8 @@ void WIIPointExtractor::draw_point(cv::Mat& preview_frame, const vec2& p, const { static constexpr int len = 9; - 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::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), @@ -48,18 +47,15 @@ void WIIPointExtractor::draw_point(cv::Mat& preview_frame, const vec2& p, const cv::Point(p2.x, p2.y + len), color, 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) { constexpr int W = 1024; constexpr int H = 768; - points.reserve(4); - points.clear(); - for (unsigned index = 0; index < 4; index++) // NOLINT(modernize-loop-convert) + 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 @@ -70,24 +66,19 @@ bool WIIPointExtractor::draw_points(cv::Mat& preview_frame, const struct wii_inf //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), clamp(dot.isize, 1, 32)); + 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) { //draw battery status - cv::line(preview_frame, - cv::Point(0, 0), - cv::Point(preview_frame.cols*wii.BatteryPercent / 100, 0), - (wii.bBatteryDrained ? cv::Scalar(255, 0, 0) : cv::Scalar(0, 140, 0)), - 2); + cv::line(preview_frame, + cv::Point(0, 0), + cv::Point(preview_frame.cols*wii.BatteryPercent / 100, 0), + (wii.bBatteryDrained ? cv::Scalar(255, 0, 0) : cv::Scalar(0, 140, 0)), + 2); //draw horizon int pdelta = iround(preview_frame.rows / 4.f * tan(wii.Pitch * pi / 180.f)); @@ -100,15 +91,46 @@ void WIIPointExtractor::draw_bg(cv::Mat& preview_frame, const struct wii_info& w 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>(); - if (wii.status == wii_cam_data_change) - { - draw_bg(preview_frame, wii); - draw_points(preview_frame, wii, points); - } + 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 4208f1b2..b8b25b1a 100644 --- a/tracker-wii/wii_point_extractor.h +++ b/tracker-wii/wii_point_extractor.h @@ -23,14 +23,18 @@ 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: pt_settings s; static void draw_point(cv::Mat& preview_frame, const vec2& p, const cv::Scalar& color, int thickness = 1); - static bool draw_points(cv::Mat& preview_frame, const struct wii_info& wii, std::vector<vec2>& points); + 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/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/trackmouse/CMakeLists.txt b/trackmouse/CMakeLists.txt deleted file mode 100644 index 6240d4b3..00000000 --- a/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(${self} opentrack-user-interface opentrack-version) diff --git a/trackmouse/images/start.png b/trackmouse/images/start.png Binary files differdeleted file mode 100644 index b8e6f271..00000000 --- a/trackmouse/images/start.png +++ /dev/null diff --git a/trackmouse/images/stop.png b/trackmouse/images/stop.png Binary files differdeleted file mode 100644 index 0ff13bd5..00000000 --- a/trackmouse/images/stop.png +++ /dev/null diff --git a/trackmouse/main.cpp b/trackmouse/main.cpp deleted file mode 100644 index 2313e1ac..00000000 --- a/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 main_window; }); -} - -#if defined _MSC_VER -int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int /* nCmdShow */) -{ - return main(__argc, __argv); -} -#endif diff --git a/trackmouse/trackmouse-res.qrc b/trackmouse/trackmouse-res.qrc deleted file mode 100644 index f351b3f2..00000000 --- a/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/trackmouse/trackmouse-settings.cpp b/trackmouse/trackmouse-settings.cpp deleted file mode 100644 index 45ec36a5..00000000 --- a/trackmouse/trackmouse-settings.cpp +++ /dev/null @@ -1,132 +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 = "pt"; - m.protocol_dll = "win32-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 these are Mini Clip Right sizes - s.clip_by = 60; - s.clip_bz = 38.2; - s.clip_ty = 42.2; - s.clip_tz = 12.6; - - s.cam_fps = 60; - s.cam_res_x = 640; - s.cam_res_y = 480; - s.camera_name = "PS3Eye Camera"; - - s.min_point_size = 3.7; - s.max_point_size = 10; - - // XXX TODO auto threshold slider position - s.auto_threshold = true; - s.threshold_slider = slider_value(82., s.threshold_slider->min(), s.threshold_slider->max()); - - 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() -{ - // XXX TODO -} - -static void force_shortcut_settings() -{ - main_settings s; - s.key_toggle_tracking1.keycode = "Ins"; - s.key_center1.keycode = "PgUp"; - s.key_toggle1.keycode = "PgDown"; - - for (key_opts* k : { &s.key_toggle_tracking1, &s.key_center1, &s.key_toggle_press1 }) - { - k->button = -1; - k->guid = {}; - } - s.b->save(); -} - -void force_trackmouse_settings() -{ - options::globals::with_settings_object([](QSettings&) { - force_main_settings(); - force_spline_settings(); - force_pt_settings(); - force_mouse_settings(); - force_accela_settings(); - force_shortcut_settings(); - }); -} diff --git a/trackmouse/trackmouse.ico b/trackmouse/trackmouse.ico Binary files differdeleted file mode 100644 index 5cac8da1..00000000 --- a/trackmouse/trackmouse.ico +++ /dev/null diff --git a/trackmouse/trackmouse.rc b/trackmouse/trackmouse.rc deleted file mode 100644 index 8df1e9b1..00000000 --- a/trackmouse/trackmouse.rc +++ /dev/null @@ -1,2 +0,0 @@ -#include <windows.h> -IDI_ICON1 ICON "trackmouse.ico" diff --git a/trackmouse/window.cpp b/trackmouse/window.cpp deleted file mode 100644 index d6c8a8dd..00000000 --- a/trackmouse/window.cpp +++ /dev/null @@ -1,388 +0,0 @@ -/* Copyright (c) 2013-2018, 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 "window.hpp" -#include "options/options.hpp" -#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 <algorithm> -#include <iterator> -#include <utility> - -#include <QMessageBox> -#include <QDir> -#include <QFile> -#include <QString> -#include <QList> -#include <QEventLoop> -#include <QApplication> - -extern "C" const char* const opentrack_version; - -using namespace options::globals; -using namespace options; - -#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 EX_OSFILE -# define EX_OSFILE 72 -#endif - -void force_trackmouse_settings(); - -main_window::main_window() : State(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH) -{ - ui.setupUi(this); - - update_button_state(false, false); - - // ctrl+q exits - connect(&kbd_quit, SIGNAL(activated()), this, SLOT(exit())); - - if (!set_profile()) - { - die_on_config_not_writable(); - exit(EX_OSFILE); - return; - } - - // only tie and connect main screen options after migrations are done - // below is fine, set_profile() is called already - - 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"; toggle_tracker_(); }, - Qt::QueuedConnection); - - connect(ui.btnStartTracker, SIGNAL(clicked()), this, SLOT(start_tracker_())); - connect(ui.btnStopTracker, SIGNAL(clicked()), this, SLOT(stop_tracker_())); - - { - tie_setting(mouse.sensitivity_x, ui.sensitivity_slider); - tie_setting(mouse.sensitivity_x, ui.sensitivity_label, - [](double x) { return QString::number(x) + QStringLiteral("%"); }); - // one-way only - tie_setting(mouse.sensitivity_x, this, - [this](double x) { mouse.sensitivity_y = *mouse.sensitivity_x; }); - - // no "ok" button, gotta save on timer - auto save = [this] { - qDebug() << "trackmouse: saving settings"; - mouse.b->save(); - save_settings_timer.stop(); - }; - - auto start_save_timer = [this](double) { - save_settings_timer.start(); - }; - - save_settings_timer.setInterval(save_settings_interval_ms); - save_settings_timer.setSingleShot(true); - - ui.sensitivity_slider->setTracking(false); - connect(&save_settings_timer, &QTimer::timeout, this, save, Qt::DirectConnection); -#if 1 - // this doesn't fire the timer on application load - connect(ui.sensitivity_slider, &QSlider::valueChanged, this, start_save_timer, Qt::DirectConnection); -#else - // but this does so let's not not use it - tie_setting(mouse.sensitivity_x, this, start_save_timer); -#endif - } - - force_trackmouse_settings(); - - register_shortcuts(); - kbd_quit.setEnabled(true); - - setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | windowFlags()); - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - adjustSize(); - - setVisible(true); - show(); -} - -void main_window::register_shortcuts() -{ - global_shortcuts.reload({ - { s.key_toggle_tracking1, [this](bool) { main_window::toggle_tracker(); }, true }, - }); - - 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\n" - "Exiting now." - ).arg(ini_directory(), pad), - QMessageBox::Close, QMessageBox::NoButton); - - exit(EX_OSFILE); -} - -bool main_window::maybe_die_on_config_not_writable(const QString& current) -{ - const bool writable = - with_settings_object([&](QSettings& s) { - return s.isWritable(); - }); - - if (writable) - return false; - - if (!QFile(ini_combine(current)).open(QFile::ReadWrite)) - { - die_on_config_not_writable(); - return true; - } - - return false; -} - -main_window::~main_window() -{ - // stupid ps3 eye has LED issues - if (work) - { - stop_tracker_(); - close(); - - constexpr int inc = 100, max = 2000; - - for (int k = 0; k < max; k += inc) - { - QEventLoop ev; - ev.processEvents(); - portable::sleep(inc); - } - } - - exit(); -} - -void main_window::set_working_directory() -{ - QDir::setCurrent(OPENTRACK_BASE_PATH); -} - -void main_window::save_modules() -{ - m.b->save(); -} - -std::tuple<main_window::dylib_ptr, int> -main_window::module_by_name(const QString& name, Modules::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, std::distance(list.cbegin(), it) }; -} - -main_window::dylib_ptr main_window::current_tracker() -{ - auto [ptr, idx] = module_by_name(m.tracker_dll, modules.trackers()); - return ptr; -} - -main_window::dylib_ptr main_window::current_protocol() -{ - auto [ptr, idx] = module_by_name(m.protocol_dll, modules.protocols()); - return ptr; -} - -main_window::dylib_ptr main_window::current_filter() -{ - auto [ptr, idx] = module_by_name(m.filter_dll, modules.filters()); - return ptr; -} - -void main_window::update_button_state(bool running, bool inertialp) -{ - bool not_running = !running; -#if 0 - 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); -#endif - 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; - - 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()); - - // 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; - - if (pTrackerDialog) - pTrackerDialog->unregister_tracker(); - - if (pProtocolDialog) - pProtocolDialog->unregister_protocol(); - - if (pFilterDialog) - pFilterDialog->unregister_filter(); - - work = nullptr; - - update_button_state(false, false); - set_title(); - ui.btnStartTracker->setFocus(); -} - -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::exit(int status) -{ - if (exiting_already) - return; - exiting_already = true; - - qDebug() << "trackmouse: saving settings on app exit"; - save_settings_timer.stop(); - mouse.b->save(); - - //close(); - QApplication::setQuitOnLastWindowClosed(true); - QApplication::exit(status); -} - -bool main_window::set_profile() -{ - if (maybe_die_on_config_not_writable(OPENTRACK_DEFAULT_CONFIG)) - return false; - - set_profile_in_registry(); - - options::detail::bundler::refresh_all_bundles(); - - // migrations are for config layout changes and other user-visible - // incompatibilities in future versions - run_migrations(); - - set_title(); - - return true; -} - -void main_window::closeEvent(QCloseEvent*) -{ - exit(); -} - -void main_window::set_profile_in_registry() -{ - with_global_settings_object([&](QSettings& s) { - s.setValue(OPENTRACK_CONFIG_FILENAME_KEY, OPENTRACK_DEFAULT_CONFIG); - }); -} - -void main_window::toggle_tracker_() -{ - qDebug() << "toggle tracker"; - if (work) - stop_tracker_(); - else - start_tracker_(); -} diff --git a/trackmouse/window.hpp b/trackmouse/window.hpp deleted file mode 100644 index 2c196852..00000000 --- a/trackmouse/window.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -/* 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. - */ - -#pragma once - -#include "ui_window.h" -#include "proto-mouse/mouse-settings.hpp" - -#include "api/plugin-support.hpp" -#include "logic/main-settings.hpp" -#include "logic/pipeline.hpp" -#include "logic/shortcuts.h" -#include "logic/work.hpp" -#include "logic/state.hpp" -#include "options/options.hpp" - -#include <tuple> -#include <memory> - -#include <QMainWindow> -#include <QKeySequence> -#include <QShortcut> -#include <QPixmap> -#include <QTimer> -#include <QString> - -class main_window final : public QMainWindow, private State -{ - Q_OBJECT - - Ui::window ui; - - QTimer save_settings_timer { this }; - - Shortcuts global_shortcuts; - module_settings m; - mouse_settings mouse; - - QShortcut kbd_quit { QKeySequence("Ctrl+Q"), this }; - std::unique_ptr<IFilterDialog> pFilterDialog; - std::unique_ptr<IProtocolDialog> pProtocolDialog; - std::unique_ptr<ITrackerDialog> pTrackerDialog; - bool exiting_already { false }; - - using dylib_ptr = Modules::dylib_ptr; - using dylib_list = Modules::dylib_list; - - static std::tuple<dylib_ptr, int> module_by_name(const QString& name, Modules::dylib_list& list); - - dylib_ptr current_tracker(); - dylib_ptr current_protocol(); - dylib_ptr current_filter(); - - void update_button_state(bool running, bool inertialp); - - void set_title(const QString& game_title = QString()); - - void set_profile_in_registry(); - void register_shortcuts(); - - void closeEvent(QCloseEvent *event) override; - - bool maybe_die_on_config_not_writable(const QString& current); - void die_on_config_not_writable(); - - static constexpr int save_settings_interval_ms = 2500; - -private slots: - void save_modules(); - void exit(int status = EXIT_SUCCESS); - bool set_profile(); - - void start_tracker_(); - void stop_tracker_(); - void toggle_tracker_(); - - static void set_working_directory(); - -signals: - void start_tracker(); - void stop_tracker(); - void toggle_tracker(); - -public: - main_window(); - ~main_window() override; -}; diff --git a/trackmouse/window.ui b/trackmouse/window.ui deleted file mode 100644 index c79ae846..00000000 --- a/trackmouse/window.ui +++ /dev/null @@ -1,458 +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"> - <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"> - <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>Insert</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </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"> - <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>Page Up</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_6"> - <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>freeze toggle</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLabel" name="label_7"> - <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>Page Down</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </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="maximum"> - <number>475</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> - <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="btnStartTracker"> - <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="btnStopTracker"> - <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/variant/default/_variant.cmake b/variant/default/_variant.cmake index 7353b0f4..e69de29b 100644 --- a/variant/default/_variant.cmake +++ b/variant/default/_variant.cmake @@ -1,32 +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" - "main-window" - "video" - "video-*" - "opentrack" - ) - - set_property(GLOBAL PROPERTY opentrack-subprojects "${subprojects}") -endfunction() diff --git a/variant/trackmouse/_variant.cmake b/variant/trackmouse/_variant.cmake deleted file mode 100644 index 1a65f6df..00000000 --- a/variant/trackmouse/_variant.cmake +++ /dev/null @@ -1,22 +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" - "spline" - "cv" - "migration" - "executable" - "pose-widget" - "trackmouse" - ) - set_property(GLOBAL PROPERTY opentrack-subprojects "${subprojects}") -endfunction() diff --git a/video-opencv/CMakeLists.txt b/video-opencv/CMakeLists.txt index c56e65d4..0b2460a4 100644 --- a/video-opencv/CMakeLists.txt +++ b/video-opencv/CMakeLists.txt @@ -2,8 +2,11 @@ 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_videoio opentrack-video) + target_link_libraries(${self} opencv_core opencv_imgcodecs opencv_videoio opentrack-video) if(WIN32) target_link_libraries(${self} strmiids) endif() diff --git a/video-opencv/impl-camera.cpp b/video-opencv/impl-camera.cpp index 3f2a1b1a..96081399 100644 --- a/video-opencv/impl-camera.cpp +++ b/video-opencv/impl-camera.cpp @@ -1,6 +1,7 @@ #include "impl.hpp" #include "compat/sleep.hpp" #include "video-property-page.hpp" +#include <opencv2/core/utils/logger.hpp> namespace opencv_camera_impl { @@ -33,7 +34,8 @@ bool cam::is_open() bool cam::start(info& args) { stop(); - cap.emplace(idx + video_capture_backend); + cv::utils::logging::setLogLevel(cv::utils::logging::LogLevel::LOG_LEVEL_WARNING); + cap.emplace(idx, video_capture_backend); if (args.width > 0 && args.height > 0) { @@ -43,6 +45,9 @@ bool cam::start(info& args) 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; diff --git a/video-opencv/impl-metadata.cpp b/video-opencv/impl-metadata.cpp index 48a2e693..7642c017 100644 --- a/video-opencv/impl-metadata.cpp +++ b/video-opencv/impl-metadata.cpp @@ -17,7 +17,11 @@ std::unique_ptr<camera> metadata::make_camera(const QString& name) std::vector<QString> metadata::camera_names() const { - return get_camera_names(); + 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) diff --git a/video-opencv/impl.hpp b/video-opencv/impl.hpp index db569823..ed5499b0 100644 --- a/video-opencv/impl.hpp +++ b/video-opencv/impl.hpp @@ -8,18 +8,13 @@ #pragma once #include "video/camera.hpp" - #include <optional> - -#include <opencv2/core.hpp> #include <opencv2/videoio.hpp> namespace opencv_camera_impl { using namespace video::impl; -struct cam; - struct metadata : camera_ { metadata(); 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/video-opencv/lang/nl_NL.ts b/video-opencv/lang/nl_NL.ts index 6401616d..9e739505 100644 --- a/video-opencv/lang/nl_NL.ts +++ b/video-opencv/lang/nl_NL.ts @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<TS version="2.1" language="nl_NL"> </TS> diff --git a/video-opencv/lang/ru_RU.ts b/video-opencv/lang/ru_RU.ts index 6401616d..f62cf2e1 100644 --- a/video-opencv/lang/ru_RU.ts +++ b/video-opencv/lang/ru_RU.ts @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<TS version="2.1" language="ru_RU"> </TS> diff --git a/video-opencv/lang/zh_CN.ts b/video-opencv/lang/zh_CN.ts index 6401616d..e5ca8aa9 100644 --- a/video-opencv/lang/zh_CN.ts +++ b/video-opencv/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/video-opencv/video-property-page.cpp b/video-opencv/video-property-page.cpp index 8057bf9e..d56d4b91 100644 --- a/video-opencv/video-property-page.cpp +++ b/video-opencv/video-property-page.cpp @@ -147,7 +147,7 @@ bool video_property_page::show(int idx) return true; } -#elif defined(__linux) +#elif defined(__linux__) # include <QProcess> # include "compat/camera-names.hpp" diff --git a/video-ps3eye/CMakeLists.txt b/video-ps3eye/CMakeLists.txt index 2fac5f9c..1f1780f9 100644 --- a/video-ps3eye/CMakeLists.txt +++ b/video-ps3eye/CMakeLists.txt @@ -10,6 +10,7 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ps3eye-driver/CMakeLists.txt") 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 "") @@ -21,26 +22,14 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ps3eye-driver/CMakeLists.txt") endif() endif() -if(TARGET ps3eye-driver) - otr_module(video-ps3eye) - link_libraries(ps3eye-driver) - add_executable(ps3eye-subprocess "wrapper.cxx") - if(WIN32) - set(path "${SDK_LIBUSB}/libusb-1.0.dll") - if(EXISTS "${path}") - otr_install_lib("${path}" "${opentrack-hier-pfx}") - endif() - endif() -endif() - -if(TARGET ps3eye-sdl) - install(TARGETS "ps3eye-sdl" DESTINATION "${opentrack-hier-pfx}") +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-hier-pfx}") + otr_install_lib("${lib}" "${opentrack-libexec}") break() endif() endforeach() @@ -48,9 +37,33 @@ if(TARGET ps3eye-sdl) endif() if(TARGET ps3eye-mode-test) - install(TARGETS "ps3eye-mode-test" DESTINATION "${opentrack-hier-pfx}") + 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-hier-pfx}") + 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/video-ps3eye/lang/nl_NL.ts b/video-ps3eye/lang/nl_NL.ts index 6401616d..f3a16fd4 100644 --- a/video-ps3eye/lang/nl_NL.ts +++ b/video-ps3eye/lang/nl_NL.ts @@ -1,4 +1,38 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<TS version="2.1" language="nl_NL"> +<context> + <name>Dialog</name> + <message> + <source>PS3 Eye</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Camera settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Exposure</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Gain</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>dialog</name> + <message> + <source>Can't open camera</source> + <translation type="unfinished"></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/lang/ru_RU.ts b/video-ps3eye/lang/ru_RU.ts index 6401616d..1edcf50d 100644 --- a/video-ps3eye/lang/ru_RU.ts +++ b/video-ps3eye/lang/ru_RU.ts @@ -1,4 +1,38 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<TS version="2.1" language="ru_RU"> +<context> + <name>Dialog</name> + <message> + <source>PS3 Eye</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Camera settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Exposure</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Gain</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>dialog</name> + <message> + <source>Can't open camera</source> + <translation type="unfinished"></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/lang/stub.ts b/video-ps3eye/lang/stub.ts index 6401616d..81ffc826 100644 --- a/video-ps3eye/lang/stub.ts +++ b/video-ps3eye/lang/stub.ts @@ -1,4 +1,38 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.1"> +<context> + <name>Dialog</name> + <message> + <source>PS3 Eye</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Camera settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Exposure</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Gain</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>dialog</name> + <message> + <source>Can't open camera</source> + <translation type="unfinished"></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/lang/zh_CN.ts b/video-ps3eye/lang/zh_CN.ts index 6401616d..9650c966 100644 --- a/video-ps3eye/lang/zh_CN.ts +++ b/video-ps3eye/lang/zh_CN.ts @@ -1,4 +1,38 @@ <?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> + <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'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 index ac6d8807..02543082 100644 --- a/video-ps3eye/module.cpp +++ b/video-ps3eye/module.cpp @@ -1,7 +1,22 @@ #include "module.hpp" -#if 0 +#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; @@ -42,16 +57,24 @@ end: 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"); -namespace video::impl { - std::vector<QString> ps3eye_camera_::camera_names() const { if (check_device_exists()) @@ -66,16 +89,219 @@ std::unique_ptr<camera> ps3eye_camera_::make_camera(const QString& name) else return {}; } -bool ps3eye_camera_::show_dialog(const QString& camera_name) + +static bool show_dialog_() { - // TODO - return false; + 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_::can_show_dialog(const QString& camera_name) + +bool ps3eye_camera_::show_dialog(const QString&) { - return false; + return show_dialog_(); +} + +bool ps3eye_camera_::can_show_dialog(const QString& name) +{ + return name == camera_name && check_device_exists(); } -} // ns video::impl +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 index 357af14c..f6934d70 100644 --- a/video-ps3eye/module.hpp +++ b/video-ps3eye/module.hpp @@ -1,54 +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" -#if 0 +#include <QDialog> +#include <QProcess> +#include <QTimer> -namespace video { +using namespace options; -struct OTR_VIDEO_EXPORT camera_ +using video::impl::camera; +using video::impl::camera_; +using video::frame; + +struct settings final { - camera_(); - virtual ~camera_(); + 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}}; - 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; + void set_exposure(); + void set_gain(); }; -struct OTR_VIDEO_EXPORT camera +class dialog final : public QWidget { - struct info final - { - // 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 - }; - - 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; + 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); }; -} // ns video +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; -#endif + 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; +}; -namespace video::impl { -struct ps3eye_camera_ : camera_ +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; }; -} // ns video::impl diff --git a/video-ps3eye/ps3eye-driver b/video-ps3eye/ps3eye-driver -Subproject 7a2dcb0ff586bd0865d35d964b75c4d0a29b4e9 +Subproject fe4eef71669bc6365a651b3c734ebd2bb5f57f8 diff --git a/video-ps3eye/shm-layout.hpp b/video-ps3eye/shm-layout.hpp index 7fa29115..65b0a4f1 100644 --- a/video-ps3eye/shm-layout.hpp +++ b/video-ps3eye/shm-layout.hpp @@ -3,37 +3,36 @@ namespace ps3eye { -struct shm_out { +static constexpr unsigned num_channels = 3; + +struct shm_in { enum class mode : uint8_t { qvga, vga, }; - enum class status : uint8_t { starting, running, fail, terminate, }; - uint8_t settings_updated; - uint16_t framerate; + uint32_t settings_updated; + uint8_t framerate, channels; mode resolution; - status status_; - uint8_t sharpness, contrast, brightness, hue, saturation; - uint8_t gain, exposure, auto_gain, awb, test_pattern; + //uint8_t sharpness, contrast, brightness hue, saturation; + uint8_t gain, exposure, auto_gain, test_pattern; + uint8_t do_exit; }; -struct shm_in { - uint8_t settings_updated_ack; - uint8_t timecode; +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][3]; - uint8_t data_640x480[640][480][3]; + uint8_t data_320x240[320][240][num_channels]; + uint8_t data_640x480[640][480][num_channels]; }; }; -struct shm { - static constexpr unsigned _cacheline_len = 64; - static constexpr unsigned _padding_len = - (_cacheline_len - (sizeof(shm_in) & (_cacheline_len - 1))) & (_cacheline_len - 1); - - using resolution = shm_out::mode; - using status = shm_out::status; - +struct alignas(64) shm { shm_out out; - const char* _padding[_padding_len]; + [[maybe_unused]] const char _padding[128 - sizeof(shm_out) % 128]; // NOLINT shm_in in; }; 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 index add62992..b7f58185 100644 --- a/video-ps3eye/wrapper.cxx +++ b/video-ps3eye/wrapper.cxx @@ -1,7 +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/camera.cpp b/video/camera.cpp index 42320402..a66d8a59 100644 --- a/video/camera.cpp +++ b/video/camera.cpp @@ -4,8 +4,12 @@ #include <utility> #include <QMutex> -static std::vector<std::unique_ptr<video::impl::camera_>> metadata; -static QMutex mtx; +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 { @@ -17,6 +21,7 @@ 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)); } @@ -27,6 +32,7 @@ namespace video { bool show_dialog(const QString& camera_name) { + auto [metadata, mtx] = get_metadata(); QMutexLocker l(&mtx); for (auto& camera : metadata) @@ -39,6 +45,7 @@ bool show_dialog(const QString& camera_name) std::unique_ptr<camera_impl> make_camera_(const QString& name) { + auto [metadata, mtx] = get_metadata(); QMutexLocker l(&mtx); for (auto& camera : metadata) @@ -54,6 +61,7 @@ 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_)) @@ -64,6 +72,8 @@ std::unique_ptr<camera_impl> make_camera(const QString& name) std::vector<QString> camera_names() { + auto [metadata, mtx] = get_metadata(); + QMutexLocker l(&mtx); std::vector<QString> names; names.reserve(32); diff --git a/video/camera.hpp b/video/camera.hpp index a0fe0adb..6181dbf3 100644 --- a/video/camera.hpp +++ b/video/camera.hpp @@ -48,11 +48,14 @@ 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(); @@ -75,12 +78,22 @@ void register_camera(std::unique_ptr<impl::camera_> metadata); static const char init_ ## ctr = \ (::video::impl::register_camera(std::make_unique<type>()), 0); -#define OTR_REGISTER_CAMERA2(type, ctr) \ - OTR_REGISTER_CAMERA3(type, ctr) +#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; 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/video/lang/nl_NL.ts b/video/lang/nl_NL.ts index 6401616d..9e739505 100644 --- a/video/lang/nl_NL.ts +++ b/video/lang/nl_NL.ts @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<TS version="2.1" language="nl_NL"> </TS> diff --git a/video/lang/ru_RU.ts b/video/lang/ru_RU.ts index 6401616d..f62cf2e1 100644 --- a/video/lang/ru_RU.ts +++ b/video/lang/ru_RU.ts @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<TS version="2.1" language="ru_RU"> </TS> diff --git a/video/lang/zh_CN.ts b/video/lang/zh_CN.ts index 6401616d..e5ca8aa9 100644 --- a/video/lang/zh_CN.ts +++ b/video/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/video/video-widget.cpp b/video/video-widget.cpp index 8262a380..3018a3c4 100644 --- a/video/video-widget.cpp +++ b/video/video-widget.cpp @@ -11,15 +11,19 @@ void video_widget::init_image_nolock() { - double dpi = screen_dpi(); + 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(65); + timer.start(15); } void video_widget::update_image(const QImage& img) @@ -84,4 +88,3 @@ 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 index 9218643b..4f54b2b9 100644 --- a/video/video-widget.hpp +++ b/video/video-widget.hpp @@ -7,7 +7,6 @@ #pragma once -#include "compat/qt-dpi.hpp" #include "compat/math.hpp" #include "export.hpp" @@ -21,7 +20,7 @@ #include <QMutex> -struct OTR_VIDEO_EXPORT video_widget : QWidget, public screen_dpi_mixin<video_widget> +struct OTR_VIDEO_EXPORT video_widget : QWidget { video_widget(QWidget* parent = nullptr); @@ -30,12 +29,12 @@ struct OTR_VIDEO_EXPORT video_widget : QWidget, public screen_dpi_mixin<video_wi 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; - bool fresh() const; void set_fresh(bool x); void set_image(const unsigned char* src, int width, int height, int stride, QImage::Format fmt); diff --git a/x-plane-plugin/CMakeLists.txt b/x-plane-plugin/CMakeLists.txt index 1d686d98..8e8fb338 100644 --- a/x-plane-plugin/CMakeLists.txt +++ b/x-plane-plugin/CMakeLists.txt @@ -2,19 +2,30 @@ if(LINUX OR APPLE) 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) target_compile_options(${self} PRIVATE -iframework "${SDK_XPLANE}/Libraries/Mac/" - -DAPL -DXPLM200 -DXPLM210 - -framework XPLM -framework XPWidgets) - target_link_options(${self} + -DAPL -DXPLM200 -DXPLM210 + -framework XPLM) + target_link_options(${self} PRIVATE "-F${SDK_XPLANE}/Libraries/Mac/" - -framework XPLM -framework XPWidgets) + -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) target_compile_options(${self} PRIVATE -DLIN -DXPLM200 -DXPLM210) target_link_options(${self} PRIVATE -rdynamic -nodefaultlibs) diff --git a/x-plane-plugin/plugin.c b/x-plane-plugin/plugin.c index ed72e50e..e43ee0ef 100644 --- a/x-plane-plugin/plugin.c +++ b/x-plane-plugin/plugin.c @@ -134,6 +134,8 @@ static int TrackToggleHandler(XPLMCommandRef inCommand, XPLMCommandPhase inPhase, void* inRefCon) { + if (inPhase != xplm_CommandBegin) return 0; + if (track_disabled) { //Enable @@ -156,6 +158,8 @@ static int TranslationToggleHandler(XPLMCommandRef inCommand, XPLMCommandPhase inPhase, void* inRefCon) { + if (inPhase != xplm_CommandBegin) return 0; + translation_disabled = !translation_disabled; if (!translation_disabled) { |