mirror of
https://github.com/hyblocker/OpenVR-SpaceCalibrator.git
synced 2026-10-23 07:01:00 +02:00
Compare commits
58
Commits
v1.4-bd_-r0
...
v1.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8100d776b8 | ||
|
|
137d8916af | ||
|
|
ceeab0e6e2 | ||
|
|
797f65ad0d | ||
|
|
3460437924 | ||
|
|
67f7c820f5 | ||
|
|
3ac6dfc208 | ||
|
|
8b86898417 | ||
|
|
d9fb08560b | ||
|
|
0ac3ab3ad8 | ||
|
|
b0fe076845 | ||
|
|
5fba766d4d | ||
|
|
9da1c41927 | ||
|
|
31fb1d15f9 | ||
|
|
16625138e1 | ||
|
|
d0159852e1 | ||
|
|
d22b2d1c69 | ||
|
|
308641ebf2 | ||
|
|
19ed75c378 | ||
|
|
4dd9ccfc79 | ||
|
|
f578999f0e | ||
|
|
7a4dc9007d | ||
|
|
02f4aa22dc | ||
|
|
89d231472a | ||
|
|
1515312e76 | ||
|
|
7349f510ef | ||
|
|
71b82d0641 | ||
|
|
68476caafc | ||
|
|
011158cd2c | ||
|
|
a8c48136c0 | ||
|
|
dc9ada1a60 | ||
|
|
3acdbe5654 | ||
|
|
de0d3425fa | ||
|
|
3b8ea60c8a | ||
|
|
56531e0432 | ||
|
|
1c18cd5445 | ||
|
|
e371b1d13f | ||
|
|
cc4ffa774d | ||
|
|
9a0204466a | ||
|
|
71e9683dc4 | ||
|
|
f21de937f0 | ||
|
|
aecc9612e2 | ||
|
|
0e8ab54400 | ||
|
|
c5c63da491 | ||
|
|
f779d68dff | ||
|
|
20a1054111 | ||
|
|
3e3d7750f1 | ||
|
|
37a951a4cc | ||
|
|
492743e4d0 | ||
|
|
d9af36f290 | ||
|
|
98760540e6 | ||
|
|
be51eeeda1 | ||
|
|
1e8191bcf1 | ||
|
|
889cdf9828 | ||
|
|
650d86dcc3 | ||
|
|
996373e07d | ||
|
|
50ecc1af1c | ||
|
|
50991f17a8 |
No files matched your search
@@ -9,3 +9,4 @@ ipch
|
||||
/x64/
|
||||
/lib/boost*
|
||||
/install/OpenVR-SpaceCalibrator.exe
|
||||
lib/Release/
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "lib/minhook"]
|
||||
path = lib/minhook
|
||||
url = https://github.com/bdunderscore/minhook.git
|
||||
@@ -1,6 +1,8 @@
|
||||
== OpenVR-SpaceCalibrator ==
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Justin Li
|
||||
Copyright (c) 2020-2022 Justin Li and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -19,3 +21,568 @@ 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.
|
||||
|
||||
This software also includes components licensed as follows:
|
||||
|
||||
== MinHook ==
|
||||
|
||||
MinHook - The Minimalistic API Hooking Library for x64/x86
|
||||
Copyright (C) 2009-2017 Tsuda Kageyu.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY 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 THE COPYRIGHT HOLDER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================================
|
||||
Portions of this software are Copyright (c) 2008-2009, Vyacheslav Patkov.
|
||||
================================================================================
|
||||
Hacker Disassembler Engine 32 C
|
||||
Copyright (c) 2008-2009, Vyacheslav Patkov.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY 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 THE REGENTS OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Hacker Disassembler Engine 64 C
|
||||
Copyright (c) 2008-2009, Vyacheslav Patkov.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY 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 THE REGENTS OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
== ImGui ==
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2022 Omar Cornut
|
||||
|
||||
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.
|
||||
|
||||
== ImPlot ==
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Evan Pezent
|
||||
|
||||
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.
|
||||
|
||||
== glfw ==
|
||||
|
||||
Copyright (c) 2002-2006 Marcus Geelnard
|
||||
Copyright (c) 2006-2016 Camilla Berglund <elmindreda@glfw.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would
|
||||
be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not
|
||||
be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
|
||||
== gl3w ==
|
||||
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
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 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.
|
||||
|
||||
== Eigen ==
|
||||
|
||||
Portions copyrighted by many parties; see source code for names.
|
||||
(The upstream distribution does not provide any single listing)
|
||||
|
||||
This application uses only the MPL-2.0 licensed components of Eigen.
|
||||
|
||||
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.
|
||||
@@ -1,28 +1,54 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.3.32819.101
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OpenVR-SpaceCalibrator", "OpenVR-SpaceCalibrator\OpenVR-SpaceCalibrator.vcxproj", "{63BAE169-D595-4464-8E7E-50D6FD379348}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OpenVR-SpaceCalibratorDriver", "OpenVR-SpaceCalibratorDriver\OpenVR-SpaceCalibratorDriver.vcxproj", "{A61324AD-CE32-46D1-A95E-7E28A6D8CCA7}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{F142A341-5EE0-442D-A15F-98AE9B48DBAE} = {F142A341-5EE0-442D-A15F-98AE9B48DBAE}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libMinHook", "lib\minhook\build\VC16\libMinHook.vcxproj", "{F142A341-5EE0-442D-A15F-98AE9B48DBAE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{63BAE169-D595-4464-8E7E-50D6FD379348}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{63BAE169-D595-4464-8E7E-50D6FD379348}.Debug|x64.Build.0 = Debug|x64
|
||||
{63BAE169-D595-4464-8E7E-50D6FD379348}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{63BAE169-D595-4464-8E7E-50D6FD379348}.Debug|x86.Build.0 = Debug|x64
|
||||
{63BAE169-D595-4464-8E7E-50D6FD379348}.Release|x64.ActiveCfg = Release|x64
|
||||
{63BAE169-D595-4464-8E7E-50D6FD379348}.Release|x64.Build.0 = Release|x64
|
||||
{63BAE169-D595-4464-8E7E-50D6FD379348}.Release|x86.ActiveCfg = Release|x64
|
||||
{63BAE169-D595-4464-8E7E-50D6FD379348}.Release|x86.Build.0 = Release|x64
|
||||
{A61324AD-CE32-46D1-A95E-7E28A6D8CCA7}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A61324AD-CE32-46D1-A95E-7E28A6D8CCA7}.Debug|x64.Build.0 = Debug|x64
|
||||
{A61324AD-CE32-46D1-A95E-7E28A6D8CCA7}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{A61324AD-CE32-46D1-A95E-7E28A6D8CCA7}.Debug|x86.Build.0 = Debug|x64
|
||||
{A61324AD-CE32-46D1-A95E-7E28A6D8CCA7}.Release|x64.ActiveCfg = Release|x64
|
||||
{A61324AD-CE32-46D1-A95E-7E28A6D8CCA7}.Release|x64.Build.0 = Release|x64
|
||||
{A61324AD-CE32-46D1-A95E-7E28A6D8CCA7}.Release|x86.ActiveCfg = Release|x64
|
||||
{A61324AD-CE32-46D1-A95E-7E28A6D8CCA7}.Release|x86.Build.0 = Release|x64
|
||||
{F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.Build.0 = Debug|x64
|
||||
{F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x86.Build.0 = Debug|Win32
|
||||
{F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.ActiveCfg = Release|x64
|
||||
{F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.Build.0 = Release|x64
|
||||
{F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x86.ActiveCfg = Release|Win32
|
||||
{F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BCE727E8-4604-4D4F-B5FF-321A7D7868D6}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "stdafx.h"
|
||||
#include "Calibration.h"
|
||||
#include "CalibrationMetrics.h"
|
||||
#include "Configuration.h"
|
||||
#include "IPCClient.h"
|
||||
#include "CalibrationCalc.h"
|
||||
#include "VRState.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -19,6 +21,10 @@ inline vr::HmdQuaternion_t operator*(const vr::HmdQuaternion_t& lhs, const vr::H
|
||||
};
|
||||
}
|
||||
|
||||
CalibrationContext CalCtx;
|
||||
IPCClient Driver;
|
||||
static protocol::DriverPoseShmem shmem;
|
||||
|
||||
namespace {
|
||||
CalibrationCalc calibration;
|
||||
|
||||
@@ -178,6 +184,13 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply tracker offsets
|
||||
if (CalCtx.state == CalibrationState::Continuous || CalCtx.state == CalibrationState::ContinuousStandby) {
|
||||
reference.vecPosition[0] += ctx.continuousCalibrationOffset.x();
|
||||
reference.vecPosition[1] += ctx.continuousCalibrationOffset.y();
|
||||
reference.vecPosition[2] += ctx.continuousCalibrationOffset.z();
|
||||
}
|
||||
|
||||
calibration.PushSample(Sample(
|
||||
ConvertPose(reference),
|
||||
ConvertPose(target)
|
||||
@@ -185,16 +198,34 @@ namespace {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AssignTargets() {
|
||||
auto state = VRState::Load();
|
||||
|
||||
if (CalCtx.referenceID < 0) {
|
||||
CalCtx.referenceID = state.FindDevice(CalCtx.referenceStandby.trackingSystem, CalCtx.referenceStandby.model, CalCtx.referenceStandby.serial);
|
||||
}
|
||||
|
||||
if (CalCtx.targetID < 0) {
|
||||
CalCtx.targetID = state.FindDevice(CalCtx.targetStandby.trackingSystem, CalCtx.targetStandby.model, CalCtx.targetStandby.serial);
|
||||
}
|
||||
|
||||
for (int i = 0; i < CalCtx.MAX_CONTROLLERS; i++) {
|
||||
if (i < state.devices.size()
|
||||
&& state.devices[i].trackingSystem == CalCtx.targetTrackingSystem
|
||||
&& state.devices[i].deviceClass == vr::TrackedDeviceClass_Controller
|
||||
&& (state.devices[i].controllerRole == vr::TrackedControllerRole_LeftHand || state.devices[i].controllerRole == vr::TrackedControllerRole_RightHand))
|
||||
{
|
||||
CalCtx.controllerIDs[i] = state.devices[i].id;
|
||||
} else {
|
||||
CalCtx.controllerIDs[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return CalCtx.referenceID >= 0 && CalCtx.targetID >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
IPCClient m_Driver;
|
||||
protocol::DriverPoseShmem m_shmem;
|
||||
CalibrationContext CalCtx;
|
||||
|
||||
|
||||
IPCClient Driver;
|
||||
static protocol::DriverPoseShmem shmem;
|
||||
|
||||
void InitCalibrator()
|
||||
{
|
||||
Driver.Connect();
|
||||
@@ -218,11 +249,14 @@ static_assert(vr::k_unTrackedDeviceIndex_Hmd == 0, "HMD index expected to be 0")
|
||||
|
||||
void ScanAndApplyProfile(CalibrationContext &ctx)
|
||||
{
|
||||
std::unique_ptr<char[]> buffer_array(new char
|
||||
[vr::k_unMaxPropertyStringSize]);
|
||||
std::unique_ptr<char[]> buffer_array(new char [vr::k_unMaxPropertyStringSize]);
|
||||
char* buffer = buffer_array.get();
|
||||
ctx.enabled = ctx.validProfile;
|
||||
|
||||
protocol::Request setParamsReq(protocol::RequestSetAlignmentSpeedParams);
|
||||
setParamsReq.setAlignmentSpeedParams = ctx.alignmentSpeedParams;
|
||||
Driver.SendBlocking(setParamsReq);
|
||||
|
||||
for (uint32_t id = 0; id < vr::k_unMaxTrackedDeviceCount; ++id)
|
||||
{
|
||||
auto deviceClass = vr::VRSystem()->GetTrackedDeviceClass(id);
|
||||
@@ -260,6 +294,22 @@ void ScanAndApplyProfile(CalibrationContext &ctx)
|
||||
//auto p = ctx.devicePoses[id].mDeviceToAbsoluteTracking.m;
|
||||
//printf("HMD %d: %f %f %f\n", id, p[0][3], p[1][3], p[2][3]);
|
||||
|
||||
// Check if the current HMD is a Pimax crystal
|
||||
if (trackingSystem == "aapvr") {
|
||||
// HMD is a Pimax HMD
|
||||
vr::HmdMatrix34_t eyeToHeadLeft = vr::VRSystem()->GetEyeToHeadTransform(vr::Eye_Left);
|
||||
// Crystal's projection matrix is constant 0s or 1s except for [0][3], which stores the IPD offset from the nose
|
||||
bool isCrystalHmd =
|
||||
eyeToHeadLeft.m[0][0] == 1 && eyeToHeadLeft.m[0][1] == 0 && eyeToHeadLeft.m[0][2] == 0 && // IPD
|
||||
eyeToHeadLeft.m[1][0] == 0 && eyeToHeadLeft.m[1][1] == 1 && eyeToHeadLeft.m[1][2] == 0 && eyeToHeadLeft.m[1][3] == 0 &&
|
||||
eyeToHeadLeft.m[2][0] == 0 && eyeToHeadLeft.m[2][1] == 0 && eyeToHeadLeft.m[2][2] == 1 && eyeToHeadLeft.m[2][3] == 0;
|
||||
|
||||
if (isCrystalHmd) {
|
||||
// Move it outside the aapvr system ; we treat aapvr as if it were lighthouse
|
||||
trackingSystem = "Pimax Crystal HMD";
|
||||
}
|
||||
}
|
||||
|
||||
if (trackingSystem != ctx.referenceTrackingSystem)
|
||||
{
|
||||
// Currently using an HMD with a different tracking system than the calibration.
|
||||
@@ -270,6 +320,23 @@ void ScanAndApplyProfile(CalibrationContext &ctx)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect Pimax crystal controllers and separate them too
|
||||
if (deviceClass == vr::TrackedDeviceClass_Controller) {
|
||||
if (trackingSystem == "oculus") {
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_RenderModelName_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
||||
std::string renderModel(buffer);
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_ConnectedWirelessDongle_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
||||
std::string connectedWirelessDongle(buffer);
|
||||
|
||||
// Check if the controller claims its an oculus controller but also pimax
|
||||
if (renderModel.find("{aapvr}") != std::string::npos &&
|
||||
renderModel.find("crystal") != std::string::npos &&
|
||||
connectedWirelessDongle.find("lighthouse") != std::string::npos) {
|
||||
trackingSystem = "Pimax Crystal Controllers";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trackingSystem != ctx.targetTrackingSystem)
|
||||
{
|
||||
ResetAndDisableOffsets(id);
|
||||
@@ -304,23 +371,35 @@ void ScanAndApplyProfile(CalibrationContext &ctx)
|
||||
}
|
||||
}
|
||||
|
||||
void StartCalibration()
|
||||
{
|
||||
void StartCalibration() {
|
||||
AssignTargets();
|
||||
CalCtx.state = CalibrationState::Begin;
|
||||
CalCtx.wantedUpdateInterval = 0.0;
|
||||
CalCtx.messages.clear();
|
||||
calibration.Clear();
|
||||
Metrics::WriteLogAnnotation("StartCalibration");
|
||||
}
|
||||
|
||||
void StartContinuousCalibration() {
|
||||
AssignTargets();
|
||||
StartCalibration();
|
||||
CalCtx.state = CalibrationState::Continuous;
|
||||
CalCtx.Log("Collecting initial samples...");
|
||||
calibration.setRelativeTransformation(CalCtx.refToTargetPose, CalCtx.relativePosCalibrated);
|
||||
calibration.lockRelativePosition = CalCtx.lockRelativePosition;
|
||||
if (CalCtx.lockRelativePosition) {
|
||||
CalCtx.Log("Relative position locked");
|
||||
}
|
||||
else {
|
||||
CalCtx.Log("Collecting initial samples...");
|
||||
}
|
||||
Metrics::WriteLogAnnotation("StartContinuousCalibration");
|
||||
}
|
||||
|
||||
void EndContinuousCalibration() {
|
||||
CalCtx.state = CalibrationState::None;
|
||||
CalCtx.relativePosCalibrated = false;
|
||||
SaveProfile(CalCtx);
|
||||
Metrics::WriteLogAnnotation("EndContinuousCalibration");
|
||||
}
|
||||
|
||||
void CalibrationTick(double time)
|
||||
@@ -343,8 +422,28 @@ void CalibrationTick(double time)
|
||||
}
|
||||
});
|
||||
|
||||
// check for non-updating headset tracking space (caused by quest out of bounds or taken off head for example) and abort everything for this tick
|
||||
auto p = ctx.devicePoses[vr::k_unTrackedDeviceIndex_Hmd].vecPosition;
|
||||
if ((p[0] == 0.0 && p[1] == 0.0 && p[2] == 0.0) || (ctx.xprev == p[0] && ctx.yprev == p[1] && ctx.zprev == p[2])) {
|
||||
// std::cerr << "HMD tracking didn't update, skipping update" << std::endl;
|
||||
return;
|
||||
}
|
||||
ctx.xprev = p[0];
|
||||
ctx.yprev = p[1];
|
||||
ctx.zprev = p[2];
|
||||
|
||||
if (ctx.state == CalibrationState::None || ctx.state == CalibrationState::ContinuousStandby
|
||||
|| (ctx.state == CalibrationState::Continuous && !calibration.isValid()))
|
||||
{
|
||||
if ((time - ctx.timeLastScan) >= 1.0)
|
||||
{
|
||||
ScanAndApplyProfile(ctx);
|
||||
ctx.timeLastScan = time;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.state == CalibrationState::ContinuousStandby) {
|
||||
if (ctx.targetID >= 0 && ctx.referenceID >= 0) {
|
||||
if (AssignTargets()) {
|
||||
StartContinuousCalibration();
|
||||
}
|
||||
else {
|
||||
@@ -354,15 +453,8 @@ void CalibrationTick(double time)
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.state == CalibrationState::None)
|
||||
{
|
||||
if (ctx.state == CalibrationState::None) {
|
||||
ctx.wantedUpdateInterval = 1.0;
|
||||
|
||||
if ((time - ctx.timeLastScan) >= 1.0)
|
||||
{
|
||||
ScanAndApplyProfile(ctx);
|
||||
ctx.timeLastScan = time;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -404,17 +496,18 @@ void CalibrationTick(double time)
|
||||
snprintf(buf, sizeof buf, "Target device ID: %d, serial %s\n", ctx.targetID, targetSerial);
|
||||
CalCtx.Log(buf);
|
||||
|
||||
if (!ctx.devicePoses[ctx.referenceID].poseIsValid)
|
||||
ScanAndApplyProfile(ctx);
|
||||
|
||||
if (!CalCtx.ReferencePoseIsValid())
|
||||
{
|
||||
CalCtx.Log("Reference device is not tracking\n"); ok = false;
|
||||
}
|
||||
|
||||
if (!ctx.devicePoses[ctx.targetID].poseIsValid)
|
||||
if (!CalCtx.TargetPoseIsValid())
|
||||
{
|
||||
CalCtx.Log("Target device is not tracking\n"); ok = false;
|
||||
}
|
||||
|
||||
|
||||
if (ok) {
|
||||
//ResetAndDisableOffsets(ctx.targetID);
|
||||
ctx.state = CalibrationState::Rotation;
|
||||
@@ -435,12 +528,33 @@ void CalibrationTick(double time)
|
||||
return;
|
||||
}
|
||||
|
||||
if (CalCtx.state == CalibrationState::Continuous && CalCtx.requireTriggerPressToApply) {
|
||||
bool triggerPressed = true;
|
||||
vr::VRControllerState_t state;
|
||||
for (int i = 0; i < CalCtx.MAX_CONTROLLERS; i++) {
|
||||
if (CalCtx.controllerIDs[i] >= 0) {
|
||||
vr::VRSystem()->GetControllerState(CalCtx.controllerIDs[i], &state, sizeof(state));
|
||||
triggerPressed &= state.rAxis[vr::k_eControllerAxis_TrackPad /* matches trigger on Index controllers?? */].x > 0.75f
|
||||
|| state.rAxis[vr::k_eControllerAxis_Trigger].x > 0.75f;
|
||||
//printf("Controller %d tracpad: %f\n", i, state.rAxis[vr::k_eControllerAxis_TrackPad].x);
|
||||
//printf("Controller %d trigger: %f\n", i, state.rAxis[vr::k_eControllerAxis_Trigger].x);
|
||||
if (!triggerPressed) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!triggerPressed) {
|
||||
ctx.wantedUpdateInterval = 0.5;
|
||||
CalCtx.Log("Waiting for trigger press...\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!CollectSample(ctx))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CalCtx.Progress(calibration.SampleCount(), CalCtx.SampleCount());
|
||||
CalCtx.Progress(calibration.SampleCount(), (int)CalCtx.SampleCount());
|
||||
|
||||
while (calibration.SampleCount() > CalCtx.SampleCount()) calibration.ShiftSample();
|
||||
|
||||
@@ -453,15 +567,20 @@ void CalibrationTick(double time)
|
||||
|
||||
if (CalCtx.state == CalibrationState::Continuous) {
|
||||
CalCtx.messages.clear();
|
||||
ok = calibration.ComputeIncremental(lerp);
|
||||
calibration.enableStaticRecalibration = CalCtx.enableStaticRecalibration;
|
||||
calibration.lockRelativePosition = CalCtx.lockRelativePosition;
|
||||
ok = calibration.ComputeIncremental(lerp, CalCtx.continuousCalibrationThreshold);
|
||||
}
|
||||
else {
|
||||
calibration.enableStaticRecalibration = false;
|
||||
ok = calibration.ComputeOneshot();
|
||||
}
|
||||
|
||||
if (calibration.isValid()) {
|
||||
ctx.calibratedRotation = calibration.EulerRotation();
|
||||
ctx.calibratedTranslation = calibration.Transformation().translation() * 100.0; // convert to cm units for profile storage
|
||||
ctx.refToTargetPose = calibration.RelativeTransformation();
|
||||
ctx.relativePosCalibrated = calibration.isRelativeTransformationCalibrated();
|
||||
|
||||
auto vrTrans = VRTranslationVec(ctx.calibratedTranslation);
|
||||
auto vrRot = VRRotationQuat(Eigen::Quaterniond(calibration.Transformation().rotation()));
|
||||
@@ -472,9 +591,7 @@ void CalibrationTick(double time)
|
||||
ScanAndApplyProfile(ctx);
|
||||
|
||||
CalCtx.Log("Finished calibration, profile saved\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
CalCtx.Log("Calibration failed.\n");
|
||||
}
|
||||
|
||||
@@ -483,17 +600,19 @@ void CalibrationTick(double time)
|
||||
LARGE_INTEGER freq;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
double duration = (end_time.QuadPart - start_time.QuadPart) / (double)freq.QuadPart;
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof buf, "Elapsed time for computation: %0.2f\n", duration * 1000.0);
|
||||
|
||||
CalCtx.Log(buf);
|
||||
Metrics::computationTime.Push(duration * 1000.0);
|
||||
|
||||
Metrics::WriteLogEntry();
|
||||
|
||||
if (CalCtx.state != CalibrationState::Continuous) {
|
||||
ctx.state = CalibrationState::None;
|
||||
calibration.Clear();
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < 10; i++) calibration.ShiftSample();
|
||||
size_t drop_samples = CalCtx.SampleCount() / 10;
|
||||
for (int i = 0; i < drop_samples; i++) {
|
||||
calibration.ShiftSample();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,8 +634,13 @@ void LoadChaperoneBounds()
|
||||
void ApplyChaperoneBounds()
|
||||
{
|
||||
vr::VRChaperoneSetup()->RevertWorkingCopy();
|
||||
vr::VRChaperoneSetup()->SetWorkingCollisionBoundsInfo(&CalCtx.chaperone.geometry[0], CalCtx.chaperone.geometry.size());
|
||||
vr::VRChaperoneSetup()->SetWorkingCollisionBoundsInfo(&CalCtx.chaperone.geometry[0], (uint32_t)CalCtx.chaperone.geometry.size());
|
||||
vr::VRChaperoneSetup()->SetWorkingStandingZeroPoseToRawTrackingPose(&CalCtx.chaperone.standingCenter);
|
||||
vr::VRChaperoneSetup()->SetWorkingPlayAreaSize(CalCtx.chaperone.playSpaceSize.v[0], CalCtx.chaperone.playSpaceSize.v[1]);
|
||||
vr::VRChaperoneSetup()->CommitWorkingCopy(vr::EChaperoneConfigFile_Live);
|
||||
}
|
||||
|
||||
void DebugApplyRandomOffset() {
|
||||
protocol::Request req(protocol::RequestDebugOffset);
|
||||
Driver.SendBlocking(req);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Eigen/Core>
|
||||
#include <Eigen/Geometry>
|
||||
#include <Windows.h>
|
||||
#include <openvr.h>
|
||||
#include <vector>
|
||||
@@ -29,6 +30,9 @@ struct CalibrationContext
|
||||
CalibrationState state = CalibrationState::None;
|
||||
int32_t referenceID = -1, targetID = -1;
|
||||
|
||||
static const size_t MAX_CONTROLLERS = 8;
|
||||
int32_t controllerIDs[MAX_CONTROLLERS];
|
||||
|
||||
StandbyDevice targetStandby, referenceStandby;
|
||||
|
||||
Eigen::Vector3d calibratedRotation;
|
||||
@@ -42,9 +46,22 @@ struct CalibrationContext
|
||||
bool validProfile = false;
|
||||
bool clearOnLog = false;
|
||||
bool quashTargetInContinuous = false;
|
||||
bool requireTriggerPressToApply = false;
|
||||
double timeLastTick = 0, timeLastScan = 0;
|
||||
double wantedUpdateInterval = 1.0;
|
||||
|
||||
float xprev, yprev, zprev;
|
||||
|
||||
float continuousCalibrationThreshold;
|
||||
Eigen::Vector3d continuousCalibrationOffset;
|
||||
|
||||
protocol::AlignmentSpeedParams alignmentSpeedParams;
|
||||
bool enableStaticRecalibration;
|
||||
bool lockRelativePosition = false;
|
||||
|
||||
Eigen::AffineCompact3d refToTargetPose = Eigen::AffineCompact3d::Identity();
|
||||
bool relativePosCalibrated = false;
|
||||
|
||||
enum Speed
|
||||
{
|
||||
FAST = 0,
|
||||
@@ -55,6 +72,32 @@ struct CalibrationContext
|
||||
|
||||
vr::DriverPose_t devicePoses[vr::k_unMaxTrackedDeviceCount];
|
||||
|
||||
CalibrationContext() {
|
||||
calibratedScale = 1.0;
|
||||
memset(devicePoses, 0, sizeof(devicePoses));
|
||||
ResetConfig();
|
||||
}
|
||||
|
||||
void ResetConfig() {
|
||||
alignmentSpeedParams.thr_rot_tiny = 0.49f * (EIGEN_PI / 180.0f);
|
||||
alignmentSpeedParams.thr_rot_small = 0.5f * (EIGEN_PI / 180.0f);
|
||||
alignmentSpeedParams.thr_rot_large = 5.0f * (EIGEN_PI / 180.0f);
|
||||
|
||||
alignmentSpeedParams.thr_trans_tiny = 0.98f / 1000.0; // mm
|
||||
alignmentSpeedParams.thr_trans_small = 1.0f / 1000.0; // mm
|
||||
alignmentSpeedParams.thr_trans_large = 20.0f / 1000.0; // mm
|
||||
|
||||
alignmentSpeedParams.align_speed_tiny = 1.0f;
|
||||
alignmentSpeedParams.align_speed_small = 1.0f;
|
||||
alignmentSpeedParams.align_speed_large = 2.0f;
|
||||
|
||||
continuousCalibrationThreshold = 1.5f;
|
||||
|
||||
continuousCalibrationOffset = Eigen::Vector3d::Zero();
|
||||
|
||||
enableStaticRecalibration = true;
|
||||
}
|
||||
|
||||
struct Chaperone
|
||||
{
|
||||
bool valid = false;
|
||||
@@ -82,6 +125,8 @@ struct CalibrationContext
|
||||
targetTrackingSystem = "";
|
||||
enabled = false;
|
||||
validProfile = false;
|
||||
refToTargetPose = Eigen::AffineCompact3d::Identity();
|
||||
relativePosCalibrated = true;
|
||||
}
|
||||
|
||||
size_t SampleCount()
|
||||
@@ -140,6 +185,16 @@ struct CalibrationContext
|
||||
messages.back().progress = current;
|
||||
messages.back().target = target;
|
||||
}
|
||||
|
||||
bool TargetPoseIsValid() const {
|
||||
return targetID >= 0 && targetID <= vr::k_unMaxTrackedDeviceCount
|
||||
&& devicePoses[targetID].poseIsValid;
|
||||
}
|
||||
|
||||
bool ReferencePoseIsValid() const {
|
||||
return referenceID >= 0 && referenceID <= vr::k_unMaxTrackedDeviceCount
|
||||
&& devicePoses[referenceID].poseIsValid;
|
||||
}
|
||||
};
|
||||
|
||||
extern CalibrationContext CalCtx;
|
||||
@@ -150,4 +205,8 @@ void StartCalibration();
|
||||
void StartContinuousCalibration();
|
||||
void EndContinuousCalibration();
|
||||
void LoadChaperoneBounds();
|
||||
void ApplyChaperoneBounds();
|
||||
void ApplyChaperoneBounds();
|
||||
|
||||
void PushCalibrationApplyTime();
|
||||
void ShowCalibrationDebug(int r, int c);
|
||||
void DebugApplyRandomOffset();
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "CalibrationCalc.h"
|
||||
#include "Calibration.h"
|
||||
#include "CalibrationMetrics.h"
|
||||
#include "..\Protocol.h"
|
||||
|
||||
inline vr::HmdQuaternion_t operator*(const vr::HmdQuaternion_t& lhs, const vr::HmdQuaternion_t& rhs) {
|
||||
@@ -106,6 +107,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
const double CalibrationCalc::AxisVarianceThreshold = 0.001;
|
||||
void CalibrationCalc::PushSample(const Sample& sample) {
|
||||
m_samples.push_back(sample);
|
||||
}
|
||||
@@ -114,6 +116,9 @@ void CalibrationCalc::Clear() {
|
||||
m_estimatedTransformation.setIdentity();
|
||||
m_isValid = false;
|
||||
m_samples.clear();
|
||||
m_axisVariance = 0.0;
|
||||
m_refToTargetPose = Eigen::AffineCompact3d::Identity();
|
||||
m_relativePosCalibrated = false;
|
||||
}
|
||||
|
||||
Eigen::Vector3d CalibrationCalc::CalibrateRotation() const {
|
||||
@@ -128,51 +133,52 @@ Eigen::Vector3d CalibrationCalc::CalibrateRotation() const {
|
||||
deltas.push_back(delta);
|
||||
}
|
||||
}
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof buf, "Got %zd samples with %zd delta samples\n", m_samples.size(), deltas.size());
|
||||
CalCtx.Log(buf);
|
||||
//char buf[256];
|
||||
//snprintf(buf, sizeof buf, "Got %zd samples with %zd delta samples\n", m_samples.size(), deltas.size());
|
||||
//CalCtx.Log(buf);
|
||||
|
||||
// Kabsch algorithm
|
||||
|
||||
Eigen::MatrixXd refPoints(deltas.size(), 3), targetPoints(deltas.size(), 3);
|
||||
Eigen::Vector3d refCentroid(0, 0, 0), targetCentroid(0, 0, 0);
|
||||
// Initialize 2D points and centroids
|
||||
Eigen::MatrixXd refPoints(deltas.size(), 2), targetPoints(deltas.size(), 2);
|
||||
Eigen::Vector2d refCentroid(0, 0), targetCentroid(0, 0);
|
||||
|
||||
for (size_t i = 0; i < deltas.size(); i++)
|
||||
{
|
||||
refPoints.row(i) = deltas[i].ref;
|
||||
refCentroid += deltas[i].ref;
|
||||
// Fill matrices and calculate centroids
|
||||
for (size_t i = 0; i < deltas.size(); i++) {
|
||||
refPoints.row(i) << deltas[i].ref[0], deltas[i].ref[2]; // Take only the x and z components
|
||||
refCentroid += refPoints.row(i);
|
||||
|
||||
targetPoints.row(i) = deltas[i].target;
|
||||
targetCentroid += deltas[i].target;
|
||||
}
|
||||
targetPoints.row(i) << deltas[i].target[0], deltas[i].target[2]; // Take only the x and z components
|
||||
targetCentroid += targetPoints.row(i);
|
||||
}
|
||||
|
||||
refCentroid /= (double)deltas.size();
|
||||
targetCentroid /= (double)deltas.size();
|
||||
refCentroid /= (double)deltas.size();
|
||||
targetCentroid /= (double)deltas.size();
|
||||
|
||||
for (size_t i = 0; i < deltas.size(); i++)
|
||||
{
|
||||
refPoints.row(i) -= refCentroid;
|
||||
targetPoints.row(i) -= targetCentroid;
|
||||
}
|
||||
// Center the points
|
||||
for (size_t i = 0; i < deltas.size(); i++) {
|
||||
refPoints.row(i) -= refCentroid;
|
||||
targetPoints.row(i) -= targetCentroid;
|
||||
}
|
||||
|
||||
auto crossCV = refPoints.transpose() * targetPoints;
|
||||
// Calculate cross-covariance matrix
|
||||
auto crossCV = refPoints.transpose() * targetPoints;
|
||||
|
||||
Eigen::BDCSVD<Eigen::MatrixXd> bdcsvd;
|
||||
auto svd = bdcsvd.compute(crossCV, Eigen::ComputeThinU | Eigen::ComputeThinV);
|
||||
// Singular Value Decomposition (SVD)
|
||||
Eigen::JacobiSVD<Eigen::MatrixXd> svd(crossCV, Eigen::ComputeThinU | Eigen::ComputeThinV);
|
||||
|
||||
Eigen::Matrix3d i = Eigen::Matrix3d::Identity();
|
||||
if ((svd.matrixU() * svd.matrixV().transpose()).determinant() < 0)
|
||||
{
|
||||
i(2, 2) = -1;
|
||||
}
|
||||
// Calculate 2D rotation matrix
|
||||
Eigen::Matrix2d i = Eigen::Matrix2d::Identity();
|
||||
Eigen::Matrix2d rot = svd.matrixV() * i * svd.matrixU().transpose();
|
||||
|
||||
Eigen::Matrix3d rot = svd.matrixV() * i * svd.matrixU().transpose();
|
||||
rot.transposeInPlace();
|
||||
// Calculate yaw angle in radians
|
||||
double yaw = std::atan2(rot(1, 0), rot(0, 0));
|
||||
|
||||
Eigen::Vector3d euler = rot.eulerAngles(2, 1, 0) * 180.0 / EIGEN_PI;
|
||||
// Convert to degrees
|
||||
Eigen::Vector3d euler(0.0, yaw * 180.0 / EIGEN_PI, 0.0);
|
||||
|
||||
snprintf(buf, sizeof buf, "Calibrated rotation: yaw=%.2f pitch=%.2f roll=%.2f\n", euler[1], euler[2], euler[0]);
|
||||
CalCtx.Log(buf);
|
||||
//snprintf(buf, sizeof buf, "Calibrated rotation: yaw=%.2f pitch=%.2f roll=%.2f\n", euler[1], euler[2], euler[0]);
|
||||
//CalCtx.Log(buf);
|
||||
return euler;
|
||||
}
|
||||
|
||||
@@ -221,12 +227,16 @@ Eigen::Vector3d CalibrationCalc::CalibrateTranslation(const Eigen::Matrix3d &rot
|
||||
Eigen::Vector3d trans = coefficients.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(constants);
|
||||
auto transcm = trans * 100.0;
|
||||
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof buf, "Calibrated translation x=%.2f y=%.2f z=%.2f\n", transcm[0], transcm[1], transcm[2]);
|
||||
CalCtx.Log(buf);
|
||||
//char buf[256];
|
||||
//snprintf(buf, sizeof buf, "Calibrated translation x=%.2f y=%.2f z=%.2f\n", transcm[0], transcm[1], transcm[2]);
|
||||
//CalCtx.Log(buf);
|
||||
return trans;
|
||||
}
|
||||
|
||||
void CalibrationCalc::CalibrateScaleOffset(const Eigen::Matrix3d& rotation, Eigen::Vector3d* out_scaleOffset, float* out_scaleFactor) const {
|
||||
// @TODO: figure out where the target and ref
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
Pose ApplyTransform(const Pose& originalPose, const Eigen::AffineCompact3d& transform) {
|
||||
@@ -333,7 +343,7 @@ Eigen::Vector4d CalibrationCalc::ComputeAxisVariance(
|
||||
|
||||
points.push_back(point);
|
||||
}
|
||||
mean /= points.size();
|
||||
mean /= (double) points.size();
|
||||
|
||||
// Compute covariance matrix
|
||||
Eigen::Matrix4d covMatrix = Eigen::Matrix4d::Zero();
|
||||
@@ -345,7 +355,7 @@ Eigen::Vector4d CalibrationCalc::ComputeAxisVariance(
|
||||
}
|
||||
}
|
||||
}
|
||||
covMatrix /= points.size();
|
||||
covMatrix /= (double) points.size();
|
||||
|
||||
Eigen::SelfAdjointEigenSolver<Eigen::Matrix4d> solver;
|
||||
solver.compute(covMatrix);
|
||||
@@ -353,29 +363,20 @@ Eigen::Vector4d CalibrationCalc::ComputeAxisVariance(
|
||||
return solver.eigenvalues();
|
||||
}
|
||||
|
||||
bool CalibrationCalc::ValidateCalibration(const Eigen::AffineCompact3d &calibration, double *error) {
|
||||
bool CalibrationCalc::ValidateCalibration(const Eigen::AffineCompact3d &calibration, double *error, Eigen::Vector3d *posOffsetV) {
|
||||
bool ok = true;
|
||||
|
||||
auto variance = ComputeAxisVariance(calibration);
|
||||
std::ostringstream oss;
|
||||
oss << "Axis variance: " << variance(0) << " " << variance(1) << " " << variance(2) << " " << variance(3) << "\n";
|
||||
CalCtx.Log(oss.str());
|
||||
oss.clear();
|
||||
|
||||
if (variance(1) < 0.0005) {
|
||||
CalCtx.Log("Calibration points are nearly coplanar. Try moving around more?\n");
|
||||
ok = false;
|
||||
}
|
||||
|
||||
const auto posOffset = ComputeRefToTargetOffset(calibration);
|
||||
char buf[256];
|
||||
|
||||
snprintf(buf, sizeof buf, "HMD to target offset: (%.2f, %.2f, %.2f)\n", posOffset(0), posOffset(1), posOffset(2));
|
||||
CalCtx.Log(buf);
|
||||
if (posOffsetV) *posOffsetV = posOffset;
|
||||
|
||||
// char buf[256];
|
||||
//snprintf(buf, sizeof buf, "HMD to target offset: (%.2f, %.2f, %.2f)\n", posOffset(0), posOffset(1), posOffset(2));
|
||||
//CalCtx.Log(buf);
|
||||
|
||||
double rmsError = RetargetingErrorRMS(posOffset, calibration);
|
||||
snprintf(buf, sizeof buf, "Position error (RMS): %.3f\n", rmsError);
|
||||
CalCtx.Log(buf);
|
||||
//snprintf(buf, sizeof buf, "Position error (RMS): %.3f\n", rmsError);
|
||||
//CalCtx.Log(buf);
|
||||
if (rmsError > 0.1) ok = false;
|
||||
|
||||
if (error) *error = rmsError;
|
||||
@@ -383,13 +384,123 @@ bool CalibrationCalc::ValidateCalibration(const Eigen::AffineCompact3d &calibrat
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
// Given:
|
||||
// R - the reference pose (in reference world space)
|
||||
// T - the target pose (in target world space)
|
||||
// C - the true calibration (target world -> reference world)
|
||||
// We assume that there is some "static target pose" S s.t.:
|
||||
// R * S = C * T (we'll call this the static target pose)
|
||||
// To compute S:
|
||||
// S = R^-1 * C * T
|
||||
// To compute C:
|
||||
// R * S * T^-1 = C
|
||||
|
||||
namespace {
|
||||
class PoseAverager {
|
||||
private:
|
||||
Eigen::Matrix<double, 4, Eigen::Dynamic> quatAvg;
|
||||
Eigen::Vector3d accum = Eigen::Vector3d::Zero();
|
||||
int i = 0;
|
||||
public:
|
||||
PoseAverager(size_t n_samples) {
|
||||
quatAvg.resize(4, n_samples);
|
||||
}
|
||||
|
||||
template<typename P>
|
||||
void Push(const P &pose) {
|
||||
const Eigen::Quaterniond rot(pose.rotation());
|
||||
quatAvg.col(i++) = Eigen::Vector4d(rot.w(), rot.x(), rot.y(), rot.z());
|
||||
accum += pose.translation();
|
||||
}
|
||||
|
||||
Eigen::AffineCompact3d Average() {
|
||||
// https://stackoverflow.com/a/27410865/36723
|
||||
auto quatT = quatAvg.transpose();
|
||||
Eigen::Matrix4d quatMul = quatAvg * quatT;
|
||||
Eigen::SelfAdjointEigenSolver<Eigen::Matrix4d> solver;
|
||||
solver.compute(quatMul);
|
||||
|
||||
Eigen::Vector4d quatAvgV = solver.eigenvectors().col(3).real().normalized();
|
||||
Eigen::Quaterniond avgQ(quatAvgV(0), quatAvgV(1), quatAvgV(2), quatAvgV(3));
|
||||
avgQ.normalize();
|
||||
|
||||
Eigen::AffineCompact3d pose(avgQ);
|
||||
pose.pretranslate(accum * (1.0 / i));
|
||||
|
||||
return pose;
|
||||
}
|
||||
|
||||
template<typename XS, typename F>
|
||||
static Eigen::AffineCompact3d AverageFor(const XS& samples, const F& poseProvider) {
|
||||
int sampleCount = 0;
|
||||
|
||||
for (auto& sample : samples) {
|
||||
if (!sample.valid) continue;
|
||||
|
||||
sampleCount++;
|
||||
}
|
||||
|
||||
PoseAverager accum(sampleCount);
|
||||
|
||||
for (auto& sample : samples) {
|
||||
if (!sample.valid) continue;
|
||||
auto pose = poseProvider(sample);
|
||||
accum.Push(pose);
|
||||
}
|
||||
|
||||
return accum.Average();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// S = R^-1 * C * T
|
||||
Eigen::AffineCompact3d CalibrationCalc::EstimateRefToTargetPose(const Eigen::AffineCompact3d &calibration) const {
|
||||
auto avg = PoseAverager::AverageFor(m_samples, [&](const auto& sample) {
|
||||
return Eigen::Affine3d(sample.ref.ToAffine().inverse() * calibration * sample.target.ToAffine());
|
||||
});
|
||||
|
||||
#if 0
|
||||
Eigen::Vector3d eulerAvgQ = avg.rotation().eulerAngles(2, 1, 0) * 180.0 / EIGEN_PI;
|
||||
Eigen::Vector3d trans = Eigen::Vector3d(avg.translation());
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "==========================================================================================\n";
|
||||
oss << "Avg rot: " << eulerAvgQ.x() << ", " << eulerAvgQ.y() << ", " << eulerAvgQ.z() << "\n";
|
||||
oss << "Avg trans:\n" << trans.x() << ", " << trans.y() << ", " << trans.z() << "\n";
|
||||
OutputDebugStringA(oss.str().c_str());
|
||||
#endif
|
||||
return avg;
|
||||
}
|
||||
|
||||
// S = R^-1 * C * T
|
||||
// R * S * T^-1 = C
|
||||
|
||||
// R * (R^-1 * C * T) * T^-1 = C
|
||||
|
||||
/*
|
||||
* This calibration routine attempts to use the estimated refToTargetPose to derive the
|
||||
* playspace calibration based on the relative position of reference and target device.
|
||||
* This computation can be performed even when the devices are not moving.
|
||||
*/
|
||||
bool CalibrationCalc::CalibrateByRelPose(Eigen::AffineCompact3d &out) const {
|
||||
// R * S * T^-1 = C
|
||||
out = PoseAverager::AverageFor(m_samples, [&](const auto& sample) {
|
||||
return Eigen::AffineCompact3d(sample.ref.ToAffine() * m_refToTargetPose * sample.target.ToAffine().inverse());
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool CalibrationCalc::ComputeOneshot() {
|
||||
auto calibration = ComputeCalibration();
|
||||
|
||||
bool valid = ValidateCalibration(calibration);
|
||||
|
||||
if (valid) {
|
||||
m_estimatedTransformation = calibration;
|
||||
m_estimatedTransformation = calibration; // @NOTE: Normal calibration
|
||||
m_isValid = true;
|
||||
return true;
|
||||
}
|
||||
@@ -399,39 +510,145 @@ bool CalibrationCalc::ComputeOneshot() {
|
||||
}
|
||||
}
|
||||
|
||||
bool CalibrationCalc::ComputeIncremental(bool &lerp) {
|
||||
auto calibration = ComputeCalibration();
|
||||
void CalibrationCalc::ComputeInstantOffset() {
|
||||
const auto &latestSample = m_samples.back();
|
||||
|
||||
// Apply transformation
|
||||
const auto updatedPose = ApplyTransform(latestSample.target, m_estimatedTransformation);
|
||||
|
||||
// Now move the transform from world to HMD space
|
||||
const auto hmdOriginPos = updatedPose.trans - latestSample.ref.trans;
|
||||
const auto hmdSpace = latestSample.ref.rot.inverse() * hmdOriginPos;
|
||||
|
||||
double newError, priorCalibrationError;
|
||||
bool valid = ValidateCalibration(calibration, &newError);
|
||||
Metrics::posOffset_lastSample.Push(hmdSpace * 1000);
|
||||
}
|
||||
|
||||
// Use stricter thresholds for continuous calibration to limit jitter
|
||||
valid = valid && newError < 0.005;
|
||||
bool CalibrationCalc::ComputeIncremental(bool &lerp, double threshold) {
|
||||
|
||||
priorCalibrationError = INFINITY;
|
||||
if (m_isValid) {
|
||||
ValidateCalibration(m_estimatedTransformation, &priorCalibrationError);
|
||||
bool oldCalibrationBetter = m_isValid && priorCalibrationError < newError * 4;
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof tmp, "Prior calibration error: %.3f (valid: %s); new error %.3f; new better? %s\n",
|
||||
priorCalibrationError, m_isValid ? "yes" : "no", newError, !oldCalibrationBetter ? "yes" : "no");
|
||||
CalCtx.Log(tmp);
|
||||
|
||||
// If we have a more noisy calibration than before, avoid updating.
|
||||
if (oldCalibrationBetter) return false;
|
||||
|
||||
Metrics::RecordTimestamp();
|
||||
|
||||
if (lockRelativePosition) {
|
||||
Eigen::AffineCompact3d byRelPose;
|
||||
double relPoseError = INFINITY;
|
||||
Eigen::Vector3d relPosOffset;
|
||||
CalibrateByRelPose(byRelPose);
|
||||
ValidateCalibration(byRelPose, &relPoseError, &relPosOffset);
|
||||
Metrics::posOffset_byRelPose.Push(relPosOffset * 1000);
|
||||
Metrics::error_byRelPose.Push(relPoseError * 1000);
|
||||
|
||||
m_isValid = true;
|
||||
m_estimatedTransformation = byRelPose;
|
||||
return true;
|
||||
}
|
||||
|
||||
double priorCalibrationError = INFINITY;
|
||||
Eigen::Vector3d priorPosOffset;
|
||||
if (m_isValid) {
|
||||
ValidateCalibration(m_estimatedTransformation, &priorCalibrationError, &priorPosOffset);
|
||||
|
||||
Metrics::posOffset_currentCal.Push(priorPosOffset * 1000);
|
||||
Metrics::error_currentCal.Push(priorCalibrationError * 1000);
|
||||
|
||||
if (priorCalibrationError < 0.005) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
double newError = INFINITY;
|
||||
bool newCalibrationValid = false;
|
||||
Eigen::AffineCompact3d byRelPose;
|
||||
Eigen::AffineCompact3d calibration;
|
||||
bool usingRelPose = false;
|
||||
double relPoseError = INFINITY;
|
||||
|
||||
if (enableStaticRecalibration && CalibrateByRelPose(byRelPose)) {
|
||||
Eigen::Vector3d relPosOffset;
|
||||
ValidateCalibration(byRelPose, &relPoseError, &relPosOffset);
|
||||
Metrics::posOffset_byRelPose.Push(relPosOffset * 1000);
|
||||
Metrics::error_byRelPose.Push(relPoseError * 1000);
|
||||
|
||||
if (relPoseError < 0.010 || m_relativePosCalibrated && relPoseError < 0.025) {
|
||||
newCalibrationValid = true;
|
||||
usingRelPose = true;
|
||||
newError = relPoseError;
|
||||
calibration = byRelPose;
|
||||
if (relPoseError * threshold >= priorCalibrationError) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double newVariance = 0;
|
||||
if (!newCalibrationValid) {
|
||||
calibration = ComputeCalibration();
|
||||
|
||||
newVariance = ComputeAxisVariance(calibration)(1);
|
||||
Metrics::axisIndependence.Push(newVariance);
|
||||
|
||||
if (newVariance < AxisVarianceThreshold && newVariance < m_axisVariance) {
|
||||
newCalibrationValid = false;
|
||||
} else {
|
||||
newCalibrationValid = ValidateCalibration(calibration, &newError, &m_posOffset);
|
||||
Metrics::posOffset_rawComputed.Push(m_posOffset * 1000);
|
||||
}
|
||||
|
||||
if (m_isValid) {
|
||||
if (priorCalibrationError < newError * threshold) {
|
||||
// If we have a more noisy calibration than before, avoid updating.
|
||||
newCalibrationValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
Metrics::error_rawComputed.Push(newError * 1000);
|
||||
|
||||
ComputeInstantOffset();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (valid) {
|
||||
|
||||
#if 0
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof tmp, "Prior calibration error: %.3f (valid: %s) sct %d; new error %.3f; new better? %s\n",
|
||||
priorCalibrationError, m_isValid ? "yes" : "no", stableCt, newError, !oldCalibrationBetter ? "yes" : "no");
|
||||
CalCtx.Log(tmp);
|
||||
#endif
|
||||
|
||||
|
||||
// Now, can we use the relative pose to perform a rapid correction?
|
||||
if (!newCalibrationValid) {
|
||||
|
||||
double existingPoseErrorUsingRelPosition = RetargetingErrorRMS(m_refToTargetPose.translation(), m_estimatedTransformation);
|
||||
Metrics::error_currentCalRelPose.Push(existingPoseErrorUsingRelPosition * 1000);
|
||||
if (relPoseError * threshold < existingPoseErrorUsingRelPosition || newCalibrationValid && relPoseError < newError) {
|
||||
newCalibrationValid = true;
|
||||
usingRelPose = true;
|
||||
newError = relPoseError;
|
||||
calibration = byRelPose;
|
||||
}
|
||||
}
|
||||
|
||||
if (newCalibrationValid) {
|
||||
lerp = m_isValid;
|
||||
m_relativePosCalibrated = m_relativePosCalibrated || newError < 0.005;
|
||||
if (!m_isValid) {
|
||||
CalCtx.Log("Applying initial transformation...");
|
||||
}
|
||||
else {
|
||||
else if (m_relativePosCalibrated) {
|
||||
CalCtx.Log("Applying updated transformation...");
|
||||
} else {
|
||||
CalCtx.Log("Applying temporary transformation...");
|
||||
}
|
||||
|
||||
m_isValid = true;
|
||||
m_estimatedTransformation = calibration;
|
||||
m_estimatedTransformation = calibration; // @NOTE: Continuous calibration
|
||||
m_axisVariance = newVariance;
|
||||
|
||||
if (!usingRelPose) {
|
||||
m_refToTargetPose = EstimateRefToTargetPose(m_estimatedTransformation);
|
||||
}
|
||||
|
||||
Metrics::calibrationApplied.Push(!usingRelPose);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,11 @@ struct Sample
|
||||
|
||||
class CalibrationCalc {
|
||||
public:
|
||||
static const double AxisVarianceThreshold;
|
||||
|
||||
bool enableStaticRecalibration;
|
||||
bool lockRelativePosition = false;
|
||||
|
||||
const Eigen::AffineCompact3d Transformation() const
|
||||
{
|
||||
return m_estimatedTransformation;
|
||||
@@ -69,12 +74,28 @@ public:
|
||||
bool isValid() const {
|
||||
return m_isValid;
|
||||
}
|
||||
|
||||
const Eigen::AffineCompact3d RelativeTransformation() const
|
||||
{
|
||||
return m_refToTargetPose;
|
||||
}
|
||||
|
||||
bool isRelativeTransformationCalibrated() const
|
||||
{
|
||||
return m_relativePosCalibrated;
|
||||
}
|
||||
|
||||
void setRelativeTransformation(const Eigen::AffineCompact3d transform, bool calibrated)
|
||||
{
|
||||
m_refToTargetPose = transform;
|
||||
m_relativePosCalibrated = calibrated;
|
||||
}
|
||||
|
||||
void PushSample(const Sample& sample);
|
||||
void Clear();
|
||||
|
||||
bool ComputeOneshot();
|
||||
bool ComputeIncremental(bool &lerp);
|
||||
bool ComputeIncremental(bool &lerp, double threshold);
|
||||
|
||||
size_t SampleCount() const {
|
||||
return m_samples.size();
|
||||
@@ -84,15 +105,29 @@ public:
|
||||
if (!m_samples.empty()) m_samples.pop_front();
|
||||
}
|
||||
|
||||
CalibrationCalc() : m_isValid(false) {}
|
||||
CalibrationCalc() : m_isValid(false), m_calcCycle(0), enableStaticRecalibration(true) {}
|
||||
|
||||
// Debug fields
|
||||
Eigen::Vector3d m_posOffset;
|
||||
double m_axisVariance = 0.0;
|
||||
long m_calcCycle;
|
||||
|
||||
private:
|
||||
bool m_isValid;
|
||||
Eigen::AffineCompact3d m_estimatedTransformation;
|
||||
bool m_relativePosCalibrated = false;
|
||||
|
||||
/*
|
||||
* This affine transform estimates the pose of the target within the reference device's local pose space.
|
||||
* That is to say, it's given by transforming the target world pose by the inverse reference pose.
|
||||
*/
|
||||
Eigen::AffineCompact3d m_refToTargetPose = Eigen::AffineCompact3d::Identity();
|
||||
|
||||
std::deque<Sample> m_samples;
|
||||
|
||||
Eigen::Vector3d CalibrateRotation() const;
|
||||
Eigen::Vector3d CalibrateTranslation(const Eigen::Matrix3d &rotation) const;
|
||||
void CalibrateScaleOffset(const Eigen::Matrix3d &rotation, Eigen::Vector3d* out_scaleOffset, float* out_scaleFactor) const;
|
||||
|
||||
Eigen::AffineCompact3d ComputeCalibration() const;
|
||||
|
||||
@@ -101,6 +136,9 @@ private:
|
||||
|
||||
Eigen::Vector4d ComputeAxisVariance(const Eigen::AffineCompact3d& calibration) const;
|
||||
|
||||
bool ValidateCalibration(const Eigen::AffineCompact3d& calibration, double *errorOut = nullptr);
|
||||
bool ValidateCalibration(const Eigen::AffineCompact3d& calibration, double *errorOut = nullptr, Eigen::Vector3d* posOffsetV = nullptr);
|
||||
void ComputeInstantOffset();
|
||||
|
||||
Eigen::AffineCompact3d EstimateRefToTargetPose(const Eigen::AffineCompact3d& calibration) const;
|
||||
bool CalibrateByRelPose(Eigen::AffineCompact3d &out) const;
|
||||
};
|
||||
@@ -0,0 +1,379 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <implot/implot.h>
|
||||
#include "CalibrationCalc.h"
|
||||
#include "CalibrationMetrics.h"
|
||||
#include "UserInterface.h"
|
||||
// ImPlotPoint (*ImPlotGetter)(void* user_data, int idx);
|
||||
namespace {
|
||||
double refTime;
|
||||
|
||||
template<typename F>
|
||||
ImPlotPoint VPIndexer(void* ptr, int idx) {
|
||||
auto point = (*reinterpret_cast<const F*>(ptr))(idx);
|
||||
point.x -= refTime;
|
||||
return point;
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void PlotLineG(const char* name, const F& f, int points) {
|
||||
const void* vp_f = &f;
|
||||
|
||||
if (points > 0) {
|
||||
ImPlot::PlotLineG(name, VPIndexer<F>, const_cast<void*>(vp_f), points);
|
||||
}
|
||||
else {
|
||||
double x = -INFINITY;
|
||||
double y = 0;
|
||||
ImPlot::PlotLine(name, &x, &y, 1);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename F, typename G>
|
||||
void PlotShadedG(const char* name, const F& data, const G& reference, int count) {
|
||||
const void* vp_data = &data;
|
||||
const void* vp_reference = &reference;
|
||||
|
||||
if (count > 0) {
|
||||
ImPlot::PlotShadedG(name,
|
||||
VPIndexer<F>, const_cast<void*>(vp_data),
|
||||
VPIndexer<G>, const_cast<void*>(vp_reference),
|
||||
count
|
||||
);
|
||||
}
|
||||
else {
|
||||
double x = -INFINITY;
|
||||
double y = 0;
|
||||
ImPlot::PlotShaded(name, &x, &y, &y, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void PlotLineG(const char* name, const Metrics::TimeSeries<double>& ts) {
|
||||
PlotLineG(name, [&](int index) {
|
||||
const auto& p = ts[index];
|
||||
return ImPlotPoint(p.first, p.second);
|
||||
},
|
||||
ts.size()
|
||||
);
|
||||
}
|
||||
|
||||
void PlotVector(const char* namePrefix, const Metrics::TimeSeries<Eigen::Vector3d>& ts) {
|
||||
std::string name(namePrefix);
|
||||
name += "X";
|
||||
PlotLineG(name.c_str(), [&](int index) {
|
||||
const auto& p = ts[index];
|
||||
return ImPlotPoint(p.first, p.second(0));
|
||||
}, ts.size());
|
||||
|
||||
name.pop_back();
|
||||
name += "Y";
|
||||
PlotLineG(name.c_str(), [&](int index) {
|
||||
const auto& p = ts[index];
|
||||
return ImPlotPoint(p.first, p.second(1));
|
||||
}, ts.size());
|
||||
|
||||
name.pop_back();
|
||||
name += "Z";
|
||||
PlotLineG(name.c_str(), [&](int index) {
|
||||
const auto& p = ts[index];
|
||||
return ImPlotPoint(p.first, p.second(2));
|
||||
}, ts.size());
|
||||
}
|
||||
|
||||
double lastMouseX = -INFINITY;
|
||||
bool wasHovered;
|
||||
|
||||
std::vector<double> calAppliedTimeBuffer, calByRelPoseTimeBuffer;
|
||||
|
||||
void PrepApplyTicks() {
|
||||
calAppliedTimeBuffer.clear();
|
||||
calByRelPoseTimeBuffer.clear();
|
||||
|
||||
for (auto t : Metrics::calibrationApplied.data()) {
|
||||
if (t.second) {
|
||||
calAppliedTimeBuffer.push_back(t.first - refTime);
|
||||
}
|
||||
else {
|
||||
calByRelPoseTimeBuffer.push_back(t.first - refTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddApplyTicks() {
|
||||
if (calAppliedTimeBuffer.empty()) {
|
||||
double x = -INFINITY;
|
||||
ImPlot::PlotVLines("##CalibrationAppliedTime", &x, 1);
|
||||
} else {
|
||||
ImPlot::PlotVLines("##CalibrationAppliedTime", &calAppliedTimeBuffer[0], (int)calAppliedTimeBuffer.size());
|
||||
}
|
||||
|
||||
if (calByRelPoseTimeBuffer.empty()) {
|
||||
double x = -INFINITY;
|
||||
ImPlot::PlotVLines("##CalibrationAppliedTimeByRelPose", &x, 1);
|
||||
}
|
||||
else {
|
||||
ImPlot::PlotVLines("##CalibrationAppliedTimeByRelPose", &calByRelPoseTimeBuffer[0], (int)calByRelPoseTimeBuffer.size());
|
||||
}
|
||||
|
||||
ImPlot::SetNextLineStyle(ImVec4(0.5, 0.5, 1, 1));
|
||||
ImPlot::PlotVLines("##TagLine", &lastMouseX, 1);
|
||||
|
||||
if (ImPlot::IsPlotHovered()) {
|
||||
auto mousePos = ImPlot::GetPlotMousePos();
|
||||
lastMouseX = mousePos.x;
|
||||
wasHovered = true;
|
||||
}
|
||||
}
|
||||
|
||||
struct GraphInfo {
|
||||
const char* name;
|
||||
void (*callback)();
|
||||
};
|
||||
|
||||
void SetupXAxis() {
|
||||
ImPlot::SetupAxisLimits(ImAxis_X1, -Metrics::TimeSpan, 0, ImGuiCond_Always);
|
||||
}
|
||||
|
||||
void G_PosOffset_RawComputed() {
|
||||
if (ImPlot::BeginPlot("##posOffsetRawComputed")) {
|
||||
ImPlot::SetupAxes(NULL, "mm", 0, ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_RangeFit);
|
||||
SetupXAxis();
|
||||
ImPlot::SetupAxisLimits(ImAxis_Y1, -200, 200, ImGuiCond_Appearing);
|
||||
|
||||
AddApplyTicks();
|
||||
|
||||
PlotVector("", Metrics::posOffset_rawComputed);
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
void G_PosOffset_CurrentCal() {
|
||||
if (ImPlot::BeginPlot("##posOffsetCurrentCal")) {
|
||||
ImPlot::SetupAxes(NULL, "mm", 0, ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_RangeFit);
|
||||
SetupXAxis();
|
||||
ImPlot::SetupAxisLimits(ImAxis_Y1, -200, 200, ImGuiCond_Appearing);
|
||||
|
||||
AddApplyTicks();
|
||||
|
||||
PlotVector("", Metrics::posOffset_currentCal);
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
void G_PosOffset_LastSample() {
|
||||
if (ImPlot::BeginPlot("##posOffsetLastSample")) {
|
||||
ImPlot::SetupAxes(NULL, "mm", 0, ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_RangeFit);
|
||||
SetupXAxis();
|
||||
ImPlot::SetupAxisLimits(ImAxis_Y1, -200, 200, ImGuiCond_Appearing);
|
||||
|
||||
AddApplyTicks();
|
||||
|
||||
PlotVector("", Metrics::posOffset_lastSample);
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
void G_PosOffset_ByRelPose() {
|
||||
if (ImPlot::BeginPlot("##posOffsetByRelPose")) {
|
||||
ImPlot::SetupAxes(NULL, "mm", 0, ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_RangeFit);
|
||||
SetupXAxis();
|
||||
ImPlot::SetupAxisLimits(ImAxis_Y1, -200, 200, ImGuiCond_Appearing);
|
||||
|
||||
AddApplyTicks();
|
||||
|
||||
PlotVector("", Metrics::posOffset_byRelPose);
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
void G_PosOffset_PosError() {
|
||||
if (ImPlot::BeginPlot("##Position error")) {
|
||||
ImPlot::SetupAxes(NULL, "mm (RMS)");
|
||||
SetupXAxis();
|
||||
ImPlot::SetupAxisLimits(ImAxis_Y1, 0, 25, ImGuiCond_Appearing);
|
||||
|
||||
AddApplyTicks();
|
||||
|
||||
PlotLineG("Candidate", Metrics::error_rawComputed);
|
||||
PlotLineG("Active", Metrics::error_currentCal);
|
||||
PlotLineG("By Rel Pose", Metrics::error_byRelPose);
|
||||
PlotLineG("CC Rel Pose", Metrics::error_currentCalRelPose);
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
void G_ComputationTime() {
|
||||
if (ImPlot::BeginPlot("##Computation Time", ImVec2(-1, 0), ImPlotFlags_NoLegend)) {
|
||||
ImPlot::SetupAxes(NULL, "ms", 0, ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_RangeFit);
|
||||
SetupXAxis();
|
||||
ImPlot::SetupAxisLimits(ImAxis_Y1, 0, 200, ImGuiCond_Appearing);
|
||||
|
||||
AddApplyTicks();
|
||||
|
||||
PlotLineG("Time", Metrics::computationTime);
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
void G_AxisVariance() {
|
||||
static bool firstrun = true;
|
||||
static ImPlotColormap axisVarianceColormap;
|
||||
if (firstrun) {
|
||||
firstrun = false;
|
||||
|
||||
auto defaultFirst = ImPlot::GetColormapColor(0);
|
||||
|
||||
ImVec4 colors[] = {
|
||||
ImPlot::GetColormapColor(0),
|
||||
ImPlot::GetColormapColor(1),
|
||||
{ 1, 0, 0, 1 },
|
||||
{ 0, 1, 0, 1 },
|
||||
{ 0.5, 0.5, 0.5, 1 },
|
||||
};
|
||||
|
||||
axisVarianceColormap = ImPlot::AddColormap("AxisVarianceColormap", colors, sizeof(colors) / sizeof(colors[0]));
|
||||
}
|
||||
|
||||
if (ImPlot::BeginPlot("##Axis variance", ImVec2(-1, 0), ImPlotFlags_NoLegend)) {
|
||||
ImPlot::SetupAxes(NULL, NULL, 0, 0);
|
||||
SetupXAxis();
|
||||
ImPlot::SetupAxisLimits(ImAxis_Y1, 0, 0.003, ImGuiCond_Always);
|
||||
|
||||
AddApplyTicks();
|
||||
|
||||
ImPlot::PushColormap(axisVarianceColormap);
|
||||
ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.5f);
|
||||
ImPlot::SetNextLineStyle(ImVec4(1, 0, 0, 1));
|
||||
PlotShadedG("##VarianceLow",
|
||||
[&](int index) {
|
||||
auto p = Metrics::axisIndependence[index];
|
||||
p.second = min(p.second, CalibrationCalc::AxisVarianceThreshold);
|
||||
return ImPlotPoint(p.first, p.second);
|
||||
},
|
||||
[&](int index) {
|
||||
auto p = Metrics::axisIndependence[index];
|
||||
return ImPlotPoint(p.first, 0);
|
||||
},
|
||||
Metrics::axisIndependence.size()
|
||||
);
|
||||
|
||||
ImPlot::SetNextLineStyle(ImVec4(0, 1, 0, 1));
|
||||
|
||||
PlotShadedG("##VarianceHigh",
|
||||
[&](int index) {
|
||||
auto p = Metrics::axisIndependence[index];
|
||||
p.second = max(p.second, CalibrationCalc::AxisVarianceThreshold);
|
||||
return ImPlotPoint(p.first, p.second);
|
||||
},
|
||||
[&](int index) {
|
||||
auto p = Metrics::axisIndependence[index];
|
||||
return ImPlotPoint(p.first, CalibrationCalc::AxisVarianceThreshold);
|
||||
},
|
||||
Metrics::axisIndependence.size()
|
||||
);
|
||||
|
||||
PlotLineG("Datapoint", Metrics::axisIndependence);
|
||||
|
||||
ImPlot::PopStyleVar(1);
|
||||
ImPlot::PopColormap(1);
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
const struct GraphInfo graphs[] = {
|
||||
{ "Position Error", G_PosOffset_PosError },
|
||||
{ "Axis Variance", G_AxisVariance },
|
||||
{ "Offset: Raw Computed", G_PosOffset_RawComputed },
|
||||
{ "Offset: Current Calibration", G_PosOffset_CurrentCal },
|
||||
{ "Offset: Last Sample", G_PosOffset_LastSample },
|
||||
{ "Offset: By Rel Pose", G_PosOffset_ByRelPose },
|
||||
{ "Processing time", G_ComputationTime }
|
||||
};
|
||||
|
||||
const int N_GRAPHS = sizeof(graphs) / sizeof(graphs[0]);
|
||||
}
|
||||
|
||||
void PushCalibrationApplyTime() {
|
||||
Metrics::calibrationApplied.Push(true);
|
||||
}
|
||||
|
||||
|
||||
void ShowCalibrationDebug(int rows, int cols) {
|
||||
static std::vector<int> curIndexes;
|
||||
|
||||
//ImGui::ShowDemoWindow();
|
||||
//ImPlot::ShowDemoWindow();
|
||||
|
||||
double initMouseX = lastMouseX;
|
||||
wasHovered = false;
|
||||
|
||||
for (int i = (int)curIndexes.size(); i < rows * cols; i++) {
|
||||
curIndexes.push_back(i % N_GRAPHS);
|
||||
}
|
||||
|
||||
auto avail = ImGui::GetContentRegionAvail();
|
||||
|
||||
auto bgCol = ImGui::GetStyleColorVec4(ImGuiCol_FrameBg);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_TableRowBg, bgCol);
|
||||
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, bgCol);
|
||||
ImPlot::PushStyleColor(ImPlotCol_FrameBg, ImVec4(0,0,0,0));
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(1);
|
||||
if (!ImGui::BeginChild("##CalibrationDebug", avail, false,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoTitleBar)) {
|
||||
ImGui::EndChild();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ImGui::BeginTable("##CalibrationDebug", cols, ImGuiTableFlags_RowBg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double t = refTime = Metrics::timestamp();
|
||||
PrepApplyTicks();
|
||||
|
||||
for (int r = 0; r < rows; r++) {
|
||||
ImGui::TableNextRow();
|
||||
for (int c = 0; c < cols; c++) {
|
||||
int i = r * cols + c;
|
||||
ImGui::TableSetColumnIndex(c);
|
||||
|
||||
ImGui::PushID(i);
|
||||
|
||||
ImGui::SetNextItemWidth(ImGui::GetColumnWidth());
|
||||
if (ImGui::BeginCombo("", graphs[curIndexes[i]].name, 0)) {
|
||||
for (int j = 0; j < N_GRAPHS; j++) {
|
||||
bool isSelected = j == curIndexes[i];
|
||||
if (ImGui::Selectable(graphs[j].name, isSelected)) {
|
||||
curIndexes[i] = j;
|
||||
}
|
||||
|
||||
if (isSelected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
graphs[curIndexes[i]].callback();
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
ImGui::EndChild();
|
||||
|
||||
ImPlot::PopStyleColor(1);
|
||||
ImGui::PopStyleColor(2);
|
||||
|
||||
if (!wasHovered) {
|
||||
lastMouseX = -INFINITY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
#include "stdafx.h"
|
||||
#include "CalibrationMetrics.h"
|
||||
#include <shlobj_core.h>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
namespace Metrics {
|
||||
double TimeSpan = 30, CurrentTime;
|
||||
|
||||
TimeSeries<Eigen::Vector3d> posOffset_rawComputed; // , rotOffset_rawComputed;
|
||||
TimeSeries<Eigen::Vector3d> posOffset_currentCal; // , rotOffset_currentCal;
|
||||
TimeSeries<Eigen::Vector3d> posOffset_lastSample; // , rotOffset_lastSample;
|
||||
TimeSeries<Eigen::Vector3d> posOffset_byRelPose;
|
||||
|
||||
TimeSeries<double> error_rawComputed, error_currentCal, error_byRelPose, error_currentCalRelPose;
|
||||
TimeSeries<double> axisIndependence;
|
||||
TimeSeries<double> computationTime;
|
||||
|
||||
// true - full calibration, false - static calibration
|
||||
TimeSeries<bool> calibrationApplied;
|
||||
|
||||
double timestamp() {
|
||||
static long long ts_start = ~0LL;
|
||||
|
||||
LARGE_INTEGER ts, freq;
|
||||
QueryPerformanceCounter(&ts);
|
||||
QueryPerformanceFrequency(&freq);
|
||||
|
||||
if (ts_start == ~0LL) ts_start = ts.QuadPart;
|
||||
|
||||
ts.QuadPart -= ts_start;
|
||||
|
||||
return ts.QuadPart / (double)freq.QuadPart;
|
||||
}
|
||||
|
||||
void RecordTimestamp() {
|
||||
CurrentTime = timestamp();
|
||||
}
|
||||
|
||||
bool enableLogs = false;
|
||||
|
||||
static std::ofstream logFile;
|
||||
static bool logFileIsOpen = false;
|
||||
static bool failedToOpenLogFile = false;
|
||||
|
||||
struct CsvField {
|
||||
const char* name;
|
||||
void (*writer)(std::ofstream& s);
|
||||
};
|
||||
|
||||
#define TS_FIELD(n) \
|
||||
{ #n, [](auto &s) { s << n.last(); } }
|
||||
|
||||
#define TS_VECTOR_FIELD(n) \
|
||||
{ #n ".x", [](auto &s) { s << n.last()(0); } }, \
|
||||
{ #n ".y", [](auto &s) { s << n.last()(1); } }, \
|
||||
{ #n ".z", [](auto &s) { s << n.last()(2); } }
|
||||
|
||||
static const CsvField fields[] = {
|
||||
{
|
||||
"Timestamp",
|
||||
[](auto& s) { s << CurrentTime; }
|
||||
},
|
||||
|
||||
TS_VECTOR_FIELD(posOffset_rawComputed),
|
||||
TS_VECTOR_FIELD(posOffset_currentCal),
|
||||
TS_VECTOR_FIELD(posOffset_lastSample),
|
||||
TS_VECTOR_FIELD(posOffset_byRelPose),
|
||||
|
||||
TS_FIELD(error_rawComputed),
|
||||
TS_FIELD(error_currentCal),
|
||||
TS_FIELD(error_byRelPose),
|
||||
TS_FIELD(error_currentCalRelPose),
|
||||
TS_FIELD(axisIndependence),
|
||||
TS_FIELD(computationTime),
|
||||
|
||||
{
|
||||
"calibrationApplied",
|
||||
[](auto& s) {
|
||||
if (calibrationApplied.lastTs() == CurrentTime) {
|
||||
if (calibrationApplied.last()) {
|
||||
s << "FULL";
|
||||
}
|
||||
else {
|
||||
s << "STATIC";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static void ClearOldLogs(const std::wstring& path) {
|
||||
std::wstring search_path = path + L"\\spacecal_log.*.txt";
|
||||
WIN32_FIND_DATA find_data;
|
||||
|
||||
SYSTEMTIME st_now;
|
||||
FILETIME ft_now;
|
||||
GetSystemTime(&st_now);
|
||||
SystemTimeToFileTime(&st_now, &ft_now);
|
||||
|
||||
ULARGE_INTEGER ft_tmp;
|
||||
ft_tmp.HighPart = ft_now.dwHighDateTime;
|
||||
ft_tmp.LowPart = ft_now.dwLowDateTime;
|
||||
// one day ago
|
||||
uint64_t limit = ft_tmp.QuadPart - (uint64_t)(24LL * 3600LL * 10LL * 1000LL * 1000LL);
|
||||
|
||||
HANDLE find_handle = FindFirstFile(search_path.c_str(), &find_data);
|
||||
if (find_handle != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
ft_tmp.HighPart = find_data.ftLastWriteTime.dwHighDateTime;
|
||||
ft_tmp.LowPart = find_data.ftLastWriteTime.dwLowDateTime;
|
||||
if (ft_tmp.QuadPart < limit) {
|
||||
std::wstring file_path = path + L"\\" + find_data.cFileName;
|
||||
DeleteFile(file_path.c_str());
|
||||
}
|
||||
} while (FindNextFile(find_handle, &find_data));
|
||||
FindClose(find_handle);
|
||||
}
|
||||
}
|
||||
|
||||
static bool OpenLogFile() {
|
||||
PWSTR RootPath = NULL;
|
||||
if (S_OK != SHGetKnownFolderPath(FOLDERID_LocalAppDataLow, 0, NULL, &RootPath)) {
|
||||
CoTaskMemFree(RootPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring path(RootPath);
|
||||
CoTaskMemFree(RootPath);
|
||||
|
||||
path += LR"(\OpenVR-SpaceCalibrator)";
|
||||
if (CreateDirectoryW(path.c_str(), 0) == 0 && GetLastError() != ERROR_ALREADY_EXISTS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
path += LR"(\Logs)";
|
||||
if (CreateDirectoryW(path.c_str(), 0) == 0 && GetLastError() != ERROR_ALREADY_EXISTS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ClearOldLogs(path);
|
||||
|
||||
SYSTEMTIME now;
|
||||
GetSystemTime(&now);
|
||||
|
||||
size_t dateBufLen = GetDateFormatW(LOCALE_USER_DEFAULT, 0, &now, L"yyyy-MM-dd", NULL, 0);
|
||||
std::vector<WCHAR> dateBuf(dateBufLen);
|
||||
if (!GetDateFormatEx(LOCALE_NAME_INVARIANT, 0, &now, L"yyyy-MM-dd", &dateBuf[0], dateBufLen, NULL)) return false;
|
||||
|
||||
size_t timeBufLen = GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &now, L"HH-mm-ss", NULL, 0);
|
||||
std::vector<WCHAR> timeBuf(timeBufLen);
|
||||
if (!GetTimeFormatEx(LOCALE_NAME_INVARIANT, 0, &now, L"HH-mm-ss", &timeBuf[0], timeBufLen)) return false;
|
||||
|
||||
path += LR"(\spacecal_log.)";
|
||||
path += &dateBuf[0];
|
||||
path += L"T";
|
||||
path += &timeBuf[0];
|
||||
path += L".txt";
|
||||
|
||||
logFile.open(path);
|
||||
if (logFile.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < sizeof fields / sizeof fields[0]; i++) {
|
||||
if (i > 0) logFile << ",";
|
||||
logFile << fields[i].name;
|
||||
}
|
||||
logFile << "\n";
|
||||
|
||||
logFileIsOpen = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool CheckLogOpen() {
|
||||
if (!enableLogs) {
|
||||
if (logFileIsOpen) {
|
||||
logFile.close();
|
||||
}
|
||||
logFileIsOpen = false;
|
||||
failedToOpenLogFile = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (failedToOpenLogFile) return false;
|
||||
if (!logFileIsOpen && !OpenLogFile()) {
|
||||
failedToOpenLogFile = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteLogAnnotation(const char *s) {
|
||||
if (!CheckLogOpen()) return;
|
||||
|
||||
logFile << "# [" << timestamp() << "] " << s << "\n";
|
||||
logFile.flush();
|
||||
}
|
||||
|
||||
void WriteLogEntry() {
|
||||
if (!CheckLogOpen()) return;
|
||||
|
||||
if (logFileIsOpen) {
|
||||
for (int i = 0; i < sizeof fields / sizeof fields[0]; i++) {
|
||||
if (i > 0) logFile << ",";
|
||||
fields[i].writer(logFile);
|
||||
}
|
||||
logFile << "\n";
|
||||
}
|
||||
logFile.flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace Metrics {
|
||||
extern double TimeSpan, CurrentTime;
|
||||
|
||||
double timestamp();
|
||||
void RecordTimestamp();
|
||||
|
||||
template<typename T>
|
||||
class TimeSeries {
|
||||
std::deque<std::pair<double, T>> Data;
|
||||
|
||||
public:
|
||||
const std::deque<std::pair<double, T>> &data() const { return Data; }
|
||||
|
||||
void Push(const T& data) {
|
||||
Data.push_back(std::make_pair(CurrentTime, data));
|
||||
|
||||
double cutoff = CurrentTime - TimeSpan;
|
||||
while (!Data.empty() && (Data.front().first < cutoff || Data.size() > INT_MAX)) {
|
||||
Data.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
int size() const { return (int)Data.size(); }
|
||||
const std::pair<double, T>& operator[](int index) const { return Data[index]; }
|
||||
|
||||
const T& last() const {
|
||||
static const T fallback;
|
||||
return Data.size() > 0 ? Data.back().second : fallback;
|
||||
}
|
||||
|
||||
const double lastTs() const {
|
||||
return Data.size() > 0 ? Data.back().first : 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
extern TimeSeries<Eigen::Vector3d> posOffset_rawComputed; // , rotOffset_rawComputed;
|
||||
extern TimeSeries<Eigen::Vector3d> posOffset_currentCal; // , rotOffset_currentCal;
|
||||
extern TimeSeries<Eigen::Vector3d> posOffset_lastSample; // , rotOffset_lastSample;
|
||||
extern TimeSeries<Eigen::Vector3d> posOffset_byRelPose;
|
||||
|
||||
extern TimeSeries<double> error_rawComputed, error_currentCal, error_byRelPose, error_currentCalRelPose;
|
||||
extern TimeSeries<double> axisIndependence;
|
||||
extern TimeSeries<double> computationTime;
|
||||
|
||||
extern TimeSeries<bool> calibrationApplied;
|
||||
|
||||
extern bool enableLogs;
|
||||
|
||||
void WriteLogAnnotation(const char* s);
|
||||
void WriteLogEntry();
|
||||
}
|
||||
@@ -46,6 +46,48 @@ static void LoadStandby(StandbyDevice& device, picojson::value& value) {
|
||||
if (serial.is<std::string>()) device.serial = serial.get<std::string>();
|
||||
}
|
||||
|
||||
static void VisitAlignmentParams(CalibrationContext& ctx, std::function<void(const char *, double&)> MapParam) {
|
||||
#define P(s) MapParam(#s, ctx.alignmentSpeedParams.s)
|
||||
P(align_speed_tiny);
|
||||
P(align_speed_small);
|
||||
P(align_speed_large);
|
||||
P(thr_trans_tiny);
|
||||
P(thr_trans_small);
|
||||
P(thr_trans_large);
|
||||
P(thr_rot_tiny);
|
||||
P(thr_rot_small);
|
||||
P(thr_rot_large);
|
||||
|
||||
// Convert to double and back
|
||||
double tmp = ctx.continuousCalibrationThreshold;
|
||||
MapParam("continuousCalibrationThreshold", tmp);
|
||||
ctx.continuousCalibrationThreshold = (float)tmp;
|
||||
}
|
||||
|
||||
static void LoadAlignmentParams(CalibrationContext& ctx, picojson::value& value) {
|
||||
ctx.ResetConfig();
|
||||
|
||||
if (!value.is<picojson::object>()) return;
|
||||
auto& obj = value.get<picojson::object>();
|
||||
|
||||
VisitAlignmentParams(ctx, [&](auto name, auto& param) {
|
||||
const picojson::value& node = obj[name];
|
||||
if (node.is<double>()) {
|
||||
param = (float)node.get<double>();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static picojson::object SaveAlignmentParams(CalibrationContext& ctx) {
|
||||
picojson::object obj;
|
||||
|
||||
VisitAlignmentParams(ctx, [&](auto name, auto& param) {
|
||||
obj[name].set<double>(param);
|
||||
});
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static void ParseProfile(CalibrationContext &ctx, std::istream &stream)
|
||||
{
|
||||
picojson::value v;
|
||||
@@ -59,6 +101,7 @@ static void ParseProfile(CalibrationContext &ctx, std::istream &stream)
|
||||
|
||||
auto obj = arr[0].get<picojson::object>();
|
||||
|
||||
LoadAlignmentParams(ctx, obj["alignment_params"]);
|
||||
ctx.referenceTrackingSystem = obj["reference_tracking_system"].get<std::string>();
|
||||
ctx.targetTrackingSystem = obj["target_tracking_system"].get<std::string>();
|
||||
ctx.calibratedRotation(0) = obj["roll"].get<double>();
|
||||
@@ -73,6 +116,10 @@ static void ParseProfile(CalibrationContext &ctx, std::istream &stream)
|
||||
ctx.state = CalibrationState::ContinuousStandby;
|
||||
}
|
||||
ctx.quashTargetInContinuous = obj["quash_target_in_continuous"].evaluate_as_boolean();
|
||||
ctx.requireTriggerPressToApply = obj["require_trigger_press_to_apply"].evaluate_as_boolean();
|
||||
ctx.continuousCalibrationOffset(0) = obj["continuous_calibration_target_offset_x"].get<double>();
|
||||
ctx.continuousCalibrationOffset(1) = obj["continuous_calibration_target_offset_y"].get<double>();
|
||||
ctx.continuousCalibrationOffset(2) = obj["continuous_calibration_target_offset_z"].get<double>();
|
||||
|
||||
if (obj["scale"].is<double>())
|
||||
ctx.calibratedScale = obj["scale"].get<double>();
|
||||
@@ -108,6 +155,34 @@ static void ParseProfile(CalibrationContext &ctx, std::istream &stream)
|
||||
ctx.chaperone.valid = true;
|
||||
}
|
||||
}
|
||||
if (obj["relative_pos_calibrated"].is<bool>()) {
|
||||
ctx.relativePosCalibrated = obj["relative_pos_calibrated"].get<bool>();
|
||||
}
|
||||
if (obj["lock_relative_position"].is<bool>()) {
|
||||
ctx.lockRelativePosition = obj["lock_relative_position"].get<bool>();
|
||||
}
|
||||
if (obj["relative_transform"].is<picojson::object>()) {
|
||||
auto relTransform = obj["relative_transform"].get<picojson::object>();
|
||||
Eigen::Vector3d refToTragetRoation;
|
||||
Eigen::Vector3d refToTargetTranslation;
|
||||
|
||||
refToTragetRoation(0) = relTransform["roll"].get<double>();
|
||||
refToTragetRoation(1) = relTransform["yaw"].get<double>();
|
||||
refToTragetRoation(2) = relTransform["pitch"].get<double>();
|
||||
refToTargetTranslation(0) = relTransform["x"].get<double>();
|
||||
refToTargetTranslation(1) = relTransform["y"].get<double>();
|
||||
refToTargetTranslation(2) = relTransform["z"].get<double>();
|
||||
|
||||
Eigen::Matrix3d rotationMatrix;
|
||||
rotationMatrix =
|
||||
Eigen::AngleAxisd(refToTragetRoation[0], Eigen::Vector3d::UnitX()) *
|
||||
Eigen::AngleAxisd(refToTragetRoation[1], Eigen::Vector3d::UnitY()) *
|
||||
Eigen::AngleAxisd(refToTragetRoation[2], Eigen::Vector3d::UnitZ());
|
||||
|
||||
ctx.refToTargetPose = Eigen::AffineCompact3d::Identity();
|
||||
ctx.refToTargetPose.linear() = rotationMatrix;
|
||||
ctx.refToTargetPose.translation() = refToTargetTranslation;
|
||||
}
|
||||
|
||||
ctx.validProfile = true;
|
||||
}
|
||||
@@ -130,6 +205,8 @@ static void WriteProfile(CalibrationContext &ctx, std::ostream &out)
|
||||
return;
|
||||
|
||||
picojson::object profile;
|
||||
profile["alignment_params"].set<picojson::object>(SaveAlignmentParams(ctx));
|
||||
|
||||
profile["reference_tracking_system"].set<std::string>(ctx.referenceTrackingSystem);
|
||||
profile["target_tracking_system"].set<std::string>(ctx.targetTrackingSystem);
|
||||
profile["roll"].set<double>(ctx.calibratedRotation(0));
|
||||
@@ -144,6 +221,10 @@ static void WriteProfile(CalibrationContext &ctx, std::ostream &out)
|
||||
bool isInContinuousCalibrationMode = ctx.state == CalibrationState::Continuous || ctx.state == CalibrationState::ContinuousStandby;
|
||||
profile["autostart_continuous_calibration"].set<bool>(isInContinuousCalibrationMode);
|
||||
profile["quash_target_in_continuous"].set<bool>(ctx.quashTargetInContinuous);
|
||||
profile["require_trigger_press_to_apply"].set<bool>(ctx.requireTriggerPressToApply);
|
||||
profile["continuous_calibration_target_offset_x"].set<double>(ctx.continuousCalibrationOffset(0));
|
||||
profile["continuous_calibration_target_offset_y"].set<double>(ctx.continuousCalibrationOffset(1));
|
||||
profile["continuous_calibration_target_offset_z"].set<double>(ctx.continuousCalibrationOffset(2));
|
||||
|
||||
double speed = (int) ctx.calibrationSpeed;
|
||||
profile["calibration_speed"].set<double>(speed);
|
||||
@@ -167,6 +248,19 @@ static void WriteProfile(CalibrationContext &ctx, std::ostream &out)
|
||||
profile["chaperone"].set<picojson::object>(chaperone);
|
||||
}
|
||||
|
||||
Eigen::Vector3d refToTragetRoation = ctx.refToTargetPose.rotation().eulerAngles(0, 1, 2);
|
||||
Eigen::Vector3d refToTargetTranslation = ctx.refToTargetPose.translation();
|
||||
picojson::object refToTarget;
|
||||
refToTarget["x"].set<double>(refToTargetTranslation(0));
|
||||
refToTarget["y"].set<double>(refToTargetTranslation(1));
|
||||
refToTarget["z"].set<double>(refToTargetTranslation(2));
|
||||
refToTarget["roll"].set<double>(refToTragetRoation(0));
|
||||
refToTarget["yaw"].set<double>(refToTragetRoation(1));
|
||||
refToTarget["pitch"].set<double>(refToTragetRoation(2));
|
||||
profile["relative_pos_calibrated"].set<bool>(ctx.relativePosCalibrated);
|
||||
profile["lock_relative_position"].set<bool>(ctx.lockRelativePosition);
|
||||
profile["relative_transform"].set<picojson::object>(refToTarget);
|
||||
|
||||
picojson::value profileV;
|
||||
profileV.set<picojson::object>(profile);
|
||||
|
||||
@@ -233,6 +327,11 @@ static void WriteRegistryKey(std::string str)
|
||||
|
||||
void LoadProfile(CalibrationContext &ctx)
|
||||
{
|
||||
// @TODO: Rewrite this to migrate configs from the registry to the spacecal directory
|
||||
// I don't know why whoever wrote this thought writing to the registry in the 2020s was a good idea...
|
||||
// NOTE: HKEY_CURRENT_USER_LOCAL_SETTINGS evaluates to HKCU\Software\Classes\Local Settings
|
||||
// Settings are currently stored at HKCU\Software\Classes\Local Settings\Software\OpenVR-SpaceCalibrator
|
||||
|
||||
ctx.validProfile = false;
|
||||
|
||||
auto str = ReadRegistryKey();
|
||||
|
||||
@@ -3,17 +3,24 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
std::string WStringToString(const std::wstring& wstr)
|
||||
{
|
||||
int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
|
||||
std::string str_to(size_needed, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &str_to[0], size_needed, NULL, NULL);
|
||||
return str_to;
|
||||
}
|
||||
|
||||
static std::string LastErrorString(DWORD lastError)
|
||||
{
|
||||
LPSTR buffer = nullptr;
|
||||
size_t size = FormatMessageA(
|
||||
LPWSTR buffer = nullptr;
|
||||
size_t size = FormatMessageW(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, lastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, NULL
|
||||
NULL, lastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&buffer, 0, NULL
|
||||
);
|
||||
|
||||
std::string message(buffer, size);
|
||||
std::wstring message(buffer, size);
|
||||
LocalFree(buffer);
|
||||
return message;
|
||||
return WStringToString(message);
|
||||
}
|
||||
|
||||
IPCClient::~IPCClient()
|
||||
@@ -37,7 +44,8 @@ void IPCClient::Connect()
|
||||
DWORD mode = PIPE_READMODE_MESSAGE;
|
||||
if (!SetNamedPipeHandleState(pipe, &mode, 0, 0))
|
||||
{
|
||||
throw std::runtime_error("Couldn't set pipe mode. Error: " + LastErrorString(GetLastError()));
|
||||
DWORD lastError = GetLastError();
|
||||
throw std::runtime_error("Couldn't set pipe mode. Error " + std::to_string(lastError) + ": " + LastErrorString(lastError));
|
||||
}
|
||||
|
||||
auto response = SendBlocking(protocol::Request(protocol::RequestHandshake));
|
||||
@@ -65,7 +73,8 @@ void IPCClient::Send(const protocol::Request &request)
|
||||
BOOL success = WriteFile(pipe, &request, sizeof request, &bytesWritten, 0);
|
||||
if (!success)
|
||||
{
|
||||
throw std::runtime_error("Error writing IPC request. Error: " + LastErrorString(GetLastError()));
|
||||
DWORD lastError = GetLastError();
|
||||
throw std::runtime_error("Error writing IPC request. Error " + std::to_string(lastError) + ": " + LastErrorString(lastError));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,13 +89,13 @@ protocol::Response IPCClient::Receive()
|
||||
DWORD lastError = GetLastError();
|
||||
if (lastError != ERROR_MORE_DATA)
|
||||
{
|
||||
throw std::runtime_error("Error reading IPC response. Error: " + LastErrorString(lastError));
|
||||
throw std::runtime_error("Error reading IPC response. Error " + std::to_string(lastError) + ": " + LastErrorString(lastError));
|
||||
}
|
||||
}
|
||||
|
||||
if (bytesRead != sizeof response)
|
||||
{
|
||||
throw std::runtime_error("Invalid IPC response with size " + std::to_string(bytesRead));
|
||||
throw std::runtime_error("Invalid IPC response. Error SIZE_MISMATCH, got size " + std::to_string(bytesRead));
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
#include <imgui/imgui_internal.h>
|
||||
#include <imgui/imgui_impl_glfw.h>
|
||||
#include <imgui/imgui_impl_opengl3.h>
|
||||
#include <implot/implot.h>
|
||||
#include <GL/gl3w.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <openvr.h>
|
||||
#include <direct.h>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
|
||||
#pragma comment(linker,"\"/manifestdependency:type='win32' \
|
||||
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
|
||||
@@ -56,6 +60,7 @@ static GLuint fboHandle = 0, fboTextureHandle = 0;
|
||||
static int fboTextureWidth = 0, fboTextureHeight = 0;
|
||||
|
||||
static char cwd[MAX_PATH];
|
||||
const float MINIMIZED_MAX_FPS = 60.0f;
|
||||
|
||||
void CreateGLFWWindow()
|
||||
{
|
||||
@@ -87,6 +92,7 @@ void CreateGLFWWindow()
|
||||
#endif
|
||||
|
||||
ImGui::CreateContext();
|
||||
ImPlot::CreateContext();
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||
@@ -205,6 +211,12 @@ void InitVR()
|
||||
|
||||
static char textBuf[0x400];
|
||||
|
||||
static bool immediateRedraw;
|
||||
void RequestImmediateRedraw() {
|
||||
immediateRedraw = true;
|
||||
}
|
||||
|
||||
double lastFrameStartTime = glfwGetTime();
|
||||
void RunLoop()
|
||||
{
|
||||
while (!glfwWindowShouldClose(glfwWindow))
|
||||
@@ -217,6 +229,7 @@ void RunLoop()
|
||||
bool dashboardVisible = false;
|
||||
int width, height;
|
||||
glfwGetFramebufferSize(glfwWindow, &width, &height);
|
||||
const bool windowVisible = (width > 0 && height > 0);
|
||||
|
||||
if (overlayMainHandle && vr::VROverlay())
|
||||
{
|
||||
@@ -247,17 +260,19 @@ void RunLoop()
|
||||
int id = ImGui::GetActiveID();
|
||||
auto textInfo = ImGui::GetInputTextState(id);
|
||||
|
||||
textBuf[0] = 0;
|
||||
int len = WideCharToMultiByte(CP_ACP, 0, (LPCWCH)textInfo->TextW.Data, textInfo->TextW.Size, textBuf, sizeof(textBuf), NULL, NULL);
|
||||
textBuf[min(len, sizeof(textBuf) - 1)] = 0;
|
||||
|
||||
uint32_t unFlags = 0; // EKeyboardFlags
|
||||
if (textInfo != nullptr) {
|
||||
textBuf[0] = 0;
|
||||
int len = WideCharToMultiByte(CP_ACP, 0, (LPCWCH)textInfo->TextW.Data, textInfo->TextW.Size, textBuf, sizeof(textBuf), NULL, NULL);
|
||||
textBuf[min(len, sizeof(textBuf) - 1)] = 0;
|
||||
|
||||
vr::VROverlay()->ShowKeyboardForOverlay(
|
||||
overlayMainHandle, vr::k_EGamepadTextInputModeNormal, vr::k_EGamepadTextInputLineModeSingleLine,
|
||||
unFlags, "Space Calibrator Overlay", sizeof textBuf, textBuf, 0
|
||||
);
|
||||
keyboardOpen = true;
|
||||
uint32_t unFlags = 0; // EKeyboardFlags
|
||||
|
||||
vr::VROverlay()->ShowKeyboardForOverlay(
|
||||
overlayMainHandle, vr::k_EGamepadTextInputModeNormal, vr::k_EGamepadTextInputLineModeSingleLine,
|
||||
unFlags, "Space Calibrator Overlay", sizeof textBuf, textBuf, 0
|
||||
);
|
||||
keyboardOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
vr::VREvent_t vrEvent;
|
||||
@@ -275,8 +290,8 @@ void RunLoop()
|
||||
break;
|
||||
case vr::VREvent_ScrollDiscrete:
|
||||
{
|
||||
double x = vrEvent.data.scroll.xdelta * 360.0f * 8.0f;
|
||||
double y = vrEvent.data.scroll.ydelta * 360.0f * 8.0f;
|
||||
float x = vrEvent.data.scroll.xdelta * 360.0f * 8.0f;
|
||||
float y = vrEvent.data.scroll.ydelta * 360.0f * 8.0f;
|
||||
io.AddMouseWheelEvent(x, y);
|
||||
break;
|
||||
}
|
||||
@@ -299,64 +314,85 @@ void RunLoop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto &io = ImGui::GetIO();
|
||||
io.DisplaySize = ImVec2((float) fboTextureWidth, (float) fboTextureHeight);
|
||||
io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
|
||||
|
||||
io.ConfigFlags = io.ConfigFlags & ~ImGuiConfigFlags_NoMouseCursorChange;
|
||||
if (dashboardVisible) {
|
||||
io.ConfigFlags = io.ConfigFlags | ImGuiConfigFlags_NoMouseCursorChange;
|
||||
}
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
BuildMainWindow(dashboardVisible);
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fboHandle);
|
||||
glViewport(0, 0, fboTextureWidth, fboTextureHeight);
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
if (width && height)
|
||||
|
||||
if (windowVisible || dashboardVisible)
|
||||
{
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fboHandle);
|
||||
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glfwSwapBuffers(glfwWindow);
|
||||
}
|
||||
auto &io = ImGui::GetIO();
|
||||
io.DisplaySize = ImVec2((float) fboTextureWidth, (float) fboTextureHeight);
|
||||
io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
|
||||
|
||||
if (dashboardVisible)
|
||||
{
|
||||
vr::Texture_t vrTex;
|
||||
vrTex.eType = vr::TextureType_OpenGL;
|
||||
vrTex.eColorSpace = vr::ColorSpace_Auto;
|
||||
io.ConfigFlags = io.ConfigFlags & ~ImGuiConfigFlags_NoMouseCursorChange;
|
||||
if (dashboardVisible) {
|
||||
io.ConfigFlags = io.ConfigFlags | ImGuiConfigFlags_NoMouseCursorChange;
|
||||
}
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
vrTex.handle = (void *)
|
||||
BuildMainWindow(dashboardVisible);
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fboHandle);
|
||||
glViewport(0, 0, fboTextureWidth, fboTextureHeight);
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
if (width && height)
|
||||
{
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fboHandle);
|
||||
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glfwSwapBuffers(glfwWindow);
|
||||
}
|
||||
|
||||
if (dashboardVisible)
|
||||
{
|
||||
vr::Texture_t vrTex;
|
||||
vrTex.eType = vr::TextureType_OpenGL;
|
||||
vrTex.eColorSpace = vr::ColorSpace_Auto;
|
||||
|
||||
vrTex.handle = (void *)
|
||||
#if defined _WIN64 || defined _LP64
|
||||
(uint64_t)
|
||||
(uint64_t)
|
||||
#endif
|
||||
fboTextureHandle;
|
||||
fboTextureHandle;
|
||||
|
||||
vr::HmdVector2_t mouseScale = { (float) fboTextureWidth, (float) fboTextureHeight };
|
||||
vr::HmdVector2_t mouseScale = { (float) fboTextureWidth, (float) fboTextureHeight };
|
||||
|
||||
vr::VROverlay()->SetOverlayTexture(overlayMainHandle, &vrTex);
|
||||
vr::VROverlay()->SetOverlayMouseScale(overlayMainHandle, &mouseScale);
|
||||
vr::VROverlay()->SetOverlayTexture(overlayMainHandle, &vrTex);
|
||||
vr::VROverlay()->SetOverlayMouseScale(overlayMainHandle, &mouseScale);
|
||||
}
|
||||
}
|
||||
|
||||
const double dashboardInterval = 1.0 / 90.0; // fps
|
||||
double waitEventsTimeout = CalCtx.wantedUpdateInterval;
|
||||
double waitEventsTimeout = max(CalCtx.wantedUpdateInterval, dashboardInterval);
|
||||
|
||||
if (dashboardVisible && waitEventsTimeout > dashboardInterval)
|
||||
waitEventsTimeout = dashboardInterval;
|
||||
|
||||
if (immediateRedraw) {
|
||||
waitEventsTimeout = 0;
|
||||
immediateRedraw = false;
|
||||
}
|
||||
|
||||
glfwWaitEventsTimeout(waitEventsTimeout);
|
||||
|
||||
// If we're minimized rendering won't limit our frame rate so we need to do it ourselves.
|
||||
if (glfwGetWindowAttrib(glfwWindow, GLFW_ICONIFIED))
|
||||
{
|
||||
double targetFrameTime = 1 / MINIMIZED_MAX_FPS;
|
||||
double waitTime = targetFrameTime - (glfwGetTime() - lastFrameStartTime);
|
||||
if (waitTime > 0)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::duration<double>(waitTime));
|
||||
}
|
||||
|
||||
lastFrameStartTime += targetFrameTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,6 +430,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
|
||||
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImPlot::DestroyContext();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
catch (std::runtime_error &e)
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>..\lib\gl3w\include;..\lib\glfw\include;..\lib\openvr;..\lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\lib\imgui;..\lib\gl3w\include;..\lib\glfw\include;..\lib\openvr;..\lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@@ -74,7 +74,7 @@
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>..\lib\gl3w\include;..\lib\glfw\include;..\lib\openvr;..\lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\lib\imgui;..\lib\gl3w\include;..\lib\glfw\include;..\lib\openvr;..\lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
@@ -86,15 +86,20 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\lib\implot\implot.h" />
|
||||
<ClInclude Include="..\lib\implot\implot_internal.h" />
|
||||
<ClInclude Include="..\Version.h" />
|
||||
<ClInclude Include="Calibration.h" />
|
||||
<ClInclude Include="CalibrationCalc.h" />
|
||||
<ClInclude Include="CalibrationMetrics.h" />
|
||||
<ClInclude Include="Configuration.h" />
|
||||
<ClInclude Include="EmbeddedFiles.h" />
|
||||
<ClInclude Include="imgui_extensions.h" />
|
||||
<ClInclude Include="IPCClient.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
<ClInclude Include="targetver.h" />
|
||||
<ClInclude Include="UserInterface.h" />
|
||||
<ClInclude Include="VRState.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\lib\gl3w\src\gl3w.c">
|
||||
@@ -103,6 +108,7 @@
|
||||
<ClCompile Include="..\lib\imgui\imgui.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\imgui\imgui_demo.cpp" />
|
||||
<ClCompile Include="..\lib\imgui\imgui_draw.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@@ -114,12 +120,18 @@
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\imgui\imgui_tables.cpp" />
|
||||
<ClCompile Include="..\lib\imgui\imgui_widgets.cpp" />
|
||||
<ClCompile Include="..\lib\implot\implot.cpp" />
|
||||
<ClCompile Include="..\lib\implot\implot_demo.cpp" />
|
||||
<ClCompile Include="..\lib\implot\implot_items.cpp" />
|
||||
<ClCompile Include="Calibration.cpp" />
|
||||
<ClCompile Include="CalibrationCalc.cpp" />
|
||||
<ClCompile Include="CalibrationDebug.cpp" />
|
||||
<ClCompile Include="CalibrationMetrics.cpp" />
|
||||
<ClCompile Include="Configuration.cpp" />
|
||||
<ClCompile Include="EmbeddedFiles.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="imgui_extensions.cpp" />
|
||||
<ClCompile Include="IPCClient.cpp" />
|
||||
<ClCompile Include="OpenVR-SpaceCalibrator.cpp" />
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
@@ -127,6 +139,7 @@
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="UserInterface.cpp" />
|
||||
<ClCompile Include="VRState.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="OpenVR-SpaceCalibrator.ico" />
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
<Filter Include="Source Files\GL">
|
||||
<UniqueIdentifier>{d156ae5c-9fe6-41f0-a34e-333dc7a6b2f8}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\ImGui\ImPlot">
|
||||
<UniqueIdentifier>{7334a0fc-18cd-446d-b621-6dadb942fe6f}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="stdafx.h">
|
||||
@@ -48,6 +51,21 @@
|
||||
<ClInclude Include="CalibrationCalc.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\implot\implot.h">
|
||||
<Filter>Source Files\ImGui\ImPlot</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\implot\implot_internal.h">
|
||||
<Filter>Source Files\ImGui\ImPlot</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="VRState.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CalibrationMetrics.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="imgui_extensions.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
@@ -95,6 +113,30 @@
|
||||
<ClCompile Include="..\lib\imgui\imgui_widgets.cpp">
|
||||
<Filter>Source Files\ImGui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\implot\implot.cpp">
|
||||
<Filter>Source Files\ImGui\ImPlot</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\implot\implot_demo.cpp">
|
||||
<Filter>Source Files\ImGui\ImPlot</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\implot\implot_items.cpp">
|
||||
<Filter>Source Files\ImGui\ImPlot</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\imgui\imgui_demo.cpp">
|
||||
<Filter>Source Files\ImGui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CalibrationDebug.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VRState.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CalibrationMetrics.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="imgui_extensions.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="small.ico">
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "UserInterface.h"
|
||||
#include "Calibration.h"
|
||||
#include "Configuration.h"
|
||||
#include "VRState.h"
|
||||
#include "CalibrationMetrics.h"
|
||||
#include "../Version.h"
|
||||
|
||||
#include <thread>
|
||||
@@ -9,22 +11,7 @@
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
struct VRDevice
|
||||
{
|
||||
int id = -1;
|
||||
vr::TrackedDeviceClass deviceClass;
|
||||
std::string model = "";
|
||||
std::string serial = "";
|
||||
std::string trackingSystem = "";
|
||||
vr::ETrackedControllerRole controllerRole = vr::TrackedControllerRole_Invalid;
|
||||
};
|
||||
|
||||
struct VRState
|
||||
{
|
||||
std::vector<std::string> trackingSystems;
|
||||
std::vector<VRDevice> devices;
|
||||
};
|
||||
#include "imgui_extensions.h"
|
||||
|
||||
void TextWithWidth(const char *label, const char *text, float width);
|
||||
|
||||
@@ -37,16 +24,27 @@ void BuildMenu(bool runningInOverlay);
|
||||
static const ImGuiWindowFlags bareWindowFlags =
|
||||
ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove;
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoScrollWithMouse |
|
||||
ImGuiWindowFlags_NoCollapse;
|
||||
|
||||
void BuildMainWindow(bool runningInOverlay)
|
||||
void BuildContinuousCalDisplay();
|
||||
void ShowVersionLine();
|
||||
|
||||
static bool runningInOverlay;
|
||||
|
||||
void BuildMainWindow(bool runningInOverlay_)
|
||||
{
|
||||
runningInOverlay = runningInOverlay_;
|
||||
bool continuousCalibration = CalCtx.state == CalibrationState::Continuous || CalCtx.state == CalibrationState::ContinuousStandby;
|
||||
|
||||
auto &io = ImGui::GetIO();
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(io.DisplaySize, ImGuiCond_Always);
|
||||
|
||||
if (!ImGui::Begin("MainWindow", nullptr, bareWindowFlags))
|
||||
if (!ImGui::Begin("OpenVRSpaceCalibrator", nullptr, bareWindowFlags))
|
||||
{
|
||||
ImGui::End();
|
||||
return;
|
||||
@@ -54,46 +52,402 @@ void BuildMainWindow(bool runningInOverlay)
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImGui::GetStyleColorVec4(ImGuiCol_Button));
|
||||
|
||||
auto state = LoadVRState();
|
||||
if (continuousCalibration) {
|
||||
BuildContinuousCalDisplay();
|
||||
}
|
||||
else {
|
||||
auto state = LoadVRState();
|
||||
|
||||
ImGui::BeginDisabled(CalCtx.state == CalibrationState::Continuous);
|
||||
BuildSystemSelection(state);
|
||||
BuildDeviceSelections(state);
|
||||
ImGui::EndDisabled();
|
||||
BuildMenu(runningInOverlay);
|
||||
ImGui::BeginDisabled(CalCtx.state == CalibrationState::Continuous);
|
||||
BuildSystemSelection(state);
|
||||
BuildDeviceSelections(state);
|
||||
ImGui::EndDisabled();
|
||||
BuildMenu(runningInOverlay);
|
||||
}
|
||||
|
||||
ShowVersionLine();
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void ShowVersionLine() {
|
||||
ImGui::SetNextWindowPos(ImVec2(10.0f, ImGui::GetWindowHeight() - ImGui::GetFrameHeightWithSpacing()));
|
||||
if (!ImGui::BeginChild("bottom line", ImVec2(ImGui::GetWindowWidth() - 20.0f, ImGui::GetFrameHeightWithSpacing() * 2), false)) {
|
||||
ImGui::EndChild();
|
||||
return;
|
||||
}
|
||||
ImGui::Text("OpenVR Space Calibrator v" SPACECAL_VERSION_STRING);
|
||||
if (runningInOverlay)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("- close VR overlay to use mouse");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void CCal_BasicInfo();
|
||||
void CCal_DrawSettings();
|
||||
|
||||
void BuildContinuousCalDisplay() {
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowSize(ImGui::GetWindowSize());
|
||||
ImGui::SetNextWindowBgAlpha(1);
|
||||
if (!ImGui::Begin("Continuous Calibration", nullptr,
|
||||
bareWindowFlags & ~ImGuiWindowFlags_NoTitleBar
|
||||
)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
ImVec2 contentRegion;
|
||||
contentRegion.x = ImGui::GetWindowContentRegionWidth();
|
||||
contentRegion.y = ImGui::GetWindowHeight() - ImGui::GetFrameHeightWithSpacing() * 2.1;
|
||||
|
||||
if (!ImGui::BeginChild("CCalDisplayFrame", contentRegion, false)) {
|
||||
ImGui::EndChild();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabBar("CCalTabs", 0)) {
|
||||
if (ImGui::BeginTabItem("Status")) {
|
||||
CCal_BasicInfo();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("More Graphs")) {
|
||||
ShowCalibrationDebug(2, 3);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("Settings")) {
|
||||
CCal_DrawSettings();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
ShowVersionLine();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
static void ScaledDragFloat(const char* label, double& f, double scale, double min, double max, int flags = ImGuiSliderFlags_AlwaysClamp) {
|
||||
float v = (float) (f * scale);
|
||||
std::string labelStr = std::string(label);
|
||||
|
||||
// If starts with ##, just do a normal SliderFloat
|
||||
if (labelStr.size() > 2 && labelStr[0] == '#' && labelStr[1] == '#') {
|
||||
ImGui::SliderFloat(label, &v, (float)min, (float)max, "%1.2f", flags);
|
||||
} else {
|
||||
// Otherwise do funny
|
||||
ImGui::Text(label);
|
||||
ImGui::SameLine();
|
||||
ImGui::PushID((std::string(label) + "_id").c_str());
|
||||
// Line up to a column, multiples of 100
|
||||
constexpr uint32_t LABEL_CURSOR = 100;
|
||||
uint32_t cursorPosX = ImGui::GetCursorPosX();
|
||||
uint32_t roundedPosition = ((cursorPosX + LABEL_CURSOR / 2) / LABEL_CURSOR) * LABEL_CURSOR;
|
||||
ImGui::SetCursorPosX(roundedPosition);
|
||||
ImGui::SliderFloat((std::string("##") + label).c_str(), &v, (float)min, (float)max, "%1.2f", flags);
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
f = v / scale;
|
||||
}
|
||||
|
||||
void DrawVectorElement(std::string id, const char* text, double* value);
|
||||
|
||||
void CCal_DrawSettings() {
|
||||
|
||||
// panel size for boxes
|
||||
ImVec2 panel_size { ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x, 0 };
|
||||
|
||||
ImGui::BeginGroupPanel("Tip", panel_size);
|
||||
ImGui::Text("Hover over settings to learn more about them!");
|
||||
ImGui::EndGroupPanel();
|
||||
|
||||
|
||||
// @TODO: Group in UI
|
||||
|
||||
// Section: Alignment speeds
|
||||
{
|
||||
ImGui::BeginGroupPanel("Calibration speeds", panel_size);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));
|
||||
ImGui::TextWrapped(
|
||||
"SpaceCalibrator uses up to three different speeds at which it drags the calibration back into "
|
||||
"position when drift occurs. These settings control how far off the calibration should be before going back to low speed (for "
|
||||
"Decel) or going to higher speeds (for Slow and Fast)."
|
||||
);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (ImGui::BeginTable("SpeedThresholds", 3, 0)) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::Text("Translation (mm)");
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ImGui::Text("Rotation (degrees)");
|
||||
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("Decel");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ScaledDragFloat("##TransDecel", CalCtx.alignmentSpeedParams.thr_trans_tiny, 1000.0, 0, 20.0);
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ScaledDragFloat("##RotDecel", CalCtx.alignmentSpeedParams.thr_rot_tiny, 180.0 / EIGEN_PI, 0, 5.0);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("Slow");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ScaledDragFloat("##TransSlow", CalCtx.alignmentSpeedParams.thr_trans_small, 1000.0,
|
||||
CalCtx.alignmentSpeedParams.thr_trans_tiny * 1000.0, 20.0);
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ScaledDragFloat("##RotSlow", CalCtx.alignmentSpeedParams.thr_rot_small, 180.0 / EIGEN_PI,
|
||||
CalCtx.alignmentSpeedParams.thr_rot_tiny * (180.0 / EIGEN_PI), 10.0);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("Fast");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ScaledDragFloat("##TransFast", CalCtx.alignmentSpeedParams.thr_trans_large, 1000.0,
|
||||
CalCtx.alignmentSpeedParams.thr_trans_small * 1000.0, 50.0);
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ScaledDragFloat("##RotFast", CalCtx.alignmentSpeedParams.thr_rot_large, 180.0 / EIGEN_PI,
|
||||
CalCtx.alignmentSpeedParams.thr_rot_small * (180.0 / EIGEN_PI), 20.0);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::EndGroupPanel();
|
||||
}
|
||||
|
||||
// Section: Alignment speeds
|
||||
{
|
||||
ImGui::BeginGroupPanel("Alignment speeds", panel_size);
|
||||
|
||||
// ImGui::Separator();
|
||||
// ImGui::Text("Alignment speeds");
|
||||
ScaledDragFloat("Decel", CalCtx.alignmentSpeedParams.align_speed_tiny, 1.0, 0, 2.0, 0);
|
||||
ScaledDragFloat("Slow", CalCtx.alignmentSpeedParams.align_speed_small, 1.0, 0, 2.0, 0);
|
||||
ScaledDragFloat("Fast", CalCtx.alignmentSpeedParams.align_speed_large, 1.0, 0, 2.0, 0);
|
||||
|
||||
ImGui::EndGroupPanel();
|
||||
}
|
||||
|
||||
|
||||
// Section: Continuous Calibration settings
|
||||
{
|
||||
ImGui::BeginGroupPanel("Continuous calibration", panel_size);
|
||||
|
||||
{
|
||||
// @TODO: Reduce code duplication (tooltips)
|
||||
// Recalibration threshold
|
||||
ImGui::Text("Recalibration threshold");
|
||||
ImGui::SameLine();
|
||||
ImGui::PushID("recalibration_threshold");
|
||||
ImGui::SliderFloat("##recalibration_threshold_slider", &CalCtx.continuousCalibrationThreshold, 1.01f, 10.0f, "%1.1f", 0);
|
||||
if (ImGui::IsItemHovered(0)) {
|
||||
ImGui::SetTooltip("Controls how good the calibration must be before realigning the trackers.\n"
|
||||
"Higher values cause calibration to happen less often, and may be useful for system with lots of tracking drift.");
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));
|
||||
ImGui::TextWrapped("Controls how often SpaceCalibrator synchronises playspaces.");
|
||||
ImGui::PopStyleColor();
|
||||
if (ImGui::IsItemHovered(0)) {
|
||||
ImGui::SetTooltip("Controls how good the calibration must be before realigning the trackers.\n"
|
||||
"Higher values cause calibration to happen less often, and may be useful for system with lots of tracking drift.");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Tracker offset
|
||||
// ImVec2 panel_size_inner { ImGui::GetCurrentWindow()->DC.ItemWidth, 0};
|
||||
ImVec2 panel_size_inner { panel_size.x - 11 * 2, 0};
|
||||
ImGui::BeginGroupPanel("Tracker offset", panel_size_inner);
|
||||
DrawVectorElement("cc_tracker_offset", "X", &CalCtx.continuousCalibrationOffset.x());
|
||||
DrawVectorElement("cc_tracker_offset", "Y", &CalCtx.continuousCalibrationOffset.y());
|
||||
DrawVectorElement("cc_tracker_offset", "Z", &CalCtx.continuousCalibrationOffset.z());
|
||||
ImGui::EndGroupPanel();
|
||||
}
|
||||
|
||||
{
|
||||
// Playspace offset
|
||||
ImVec2 panel_size_inner{ panel_size.x - 11 * 2, 0 };
|
||||
ImGui::BeginGroupPanel("Playspace scale", panel_size_inner);
|
||||
DrawVectorElement("cc_playspace_scale", "PLayspace Scale", &CalCtx.calibratedScale);
|
||||
ImGui::EndGroupPanel();
|
||||
}
|
||||
|
||||
ImGui::EndGroupPanel();
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
ImGui::Indent();
|
||||
if (ImGui::Button("Reset settings")) {
|
||||
CalCtx.ResetConfig();
|
||||
}
|
||||
ImGui::Unindent();
|
||||
ImGui::NewLine();
|
||||
|
||||
// Section: Contributors credits
|
||||
{
|
||||
ImGui::BeginGroupPanel("Credits", panel_size);
|
||||
|
||||
ImGui::TextDisabled("tach");
|
||||
ImGui::TextDisabled("pushrax");
|
||||
ImGui::TextDisabled("bd_");
|
||||
ImGui::TextDisabled("ArcticFox");
|
||||
ImGui::TextDisabled("hekky");
|
||||
|
||||
ImGui::EndGroupPanel();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawVectorElement(std::string id, const char* text, double* value) {
|
||||
constexpr float CONTINUOUS_CALIBRATION_TRACKER_OFFSET_DELTA = 0.01f;
|
||||
|
||||
ImGui::Text(text);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::PushID((id + text + "_btn_reset").c_str());
|
||||
if (ImGui::Button(" 0 ")) {
|
||||
*value *= 0;
|
||||
}
|
||||
ImGui::PopID();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::ArrowButton((id + text + "_decrease").c_str(), ImGuiDir_Down)) {
|
||||
*value -= CONTINUOUS_CALIBRATION_TRACKER_OFFSET_DELTA;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(100);
|
||||
ImGui::PushID((id + text + "_text_field").c_str());
|
||||
ImGui::InputDouble("##label", value, 0, 0, "%.2f");
|
||||
ImGui::PopID();
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::ArrowButton((id + text + "_increase").c_str(), ImGuiDir_Up)) {
|
||||
*value += CONTINUOUS_CALIBRATION_TRACKER_OFFSET_DELTA;
|
||||
}
|
||||
}
|
||||
|
||||
void CCal_BasicInfo() {
|
||||
if (ImGui::BeginTable("DeviceInfo", 2, 0)) {
|
||||
ImGui::TableSetupColumn("Reference device");
|
||||
ImGui::TableSetupColumn("Target device");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::BeginGroup();
|
||||
ImGui::Text("%s / %s / %s",
|
||||
CalCtx.referenceStandby.trackingSystem.c_str(),
|
||||
CalCtx.referenceStandby.model.c_str(),
|
||||
CalCtx.referenceStandby.serial.c_str()
|
||||
);
|
||||
const char* status;
|
||||
if (CalCtx.referenceID < 0) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, 0xFF000080);
|
||||
status = "NOT FOUND";
|
||||
}
|
||||
else if (!CalCtx.ReferencePoseIsValid()) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, 0xFFFF0080);
|
||||
status = "NOT TRACKING";
|
||||
}
|
||||
else {
|
||||
status = "OK";
|
||||
}
|
||||
ImGui::Text("Status: %s", status);
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::BeginGroup();
|
||||
ImGui::Text("%s / %s / %s",
|
||||
CalCtx.targetStandby.trackingSystem.c_str(),
|
||||
CalCtx.targetStandby.model.c_str(),
|
||||
CalCtx.targetStandby.serial.c_str()
|
||||
);
|
||||
if (CalCtx.targetID < 0) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, 0xFF000080);
|
||||
status = "NOT FOUND";
|
||||
}
|
||||
else if (!CalCtx.TargetPoseIsValid()) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, 0xFFFF0080);
|
||||
status = "NOT TRACKING";
|
||||
}
|
||||
else {
|
||||
status = "OK";
|
||||
}
|
||||
ImGui::Text("Status: %s", status);
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
float width = ImGui::GetWindowContentRegionWidth(), scale = 1.0f;
|
||||
|
||||
if (ImGui::BeginTable("##CCal_Cancel", Metrics::enableLogs ? 3 : 2, 0, ImVec2(width * scale, ImGui::GetTextLineHeight() * 2))) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
if (ImGui::Button("Cancel Continuous Calibration", ImVec2(-FLT_MIN, 0.0f))) {
|
||||
EndContinuousCalibration();
|
||||
}
|
||||
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
if (ImGui::Button("Debug: Force break calibration", ImVec2(-FLT_MIN, 0.0f))) {
|
||||
DebugApplyRandomOffset();
|
||||
}
|
||||
|
||||
if (Metrics::enableLogs) {
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
if (ImGui::Button("Debug: Mark logs", ImVec2(-FLT_MIN, 0.0f))) {
|
||||
Metrics::WriteLogAnnotation("MARK LOGS");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Hide tracker", &CalCtx.quashTargetInContinuous);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Static recalibration", &CalCtx.enableStaticRecalibration);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Enable debug logs", &Metrics::enableLogs);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Lock relative transform", &CalCtx.lockRelativePosition);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Require triggers", &CalCtx.requireTriggerPressToApply);
|
||||
|
||||
// Status field...
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0, 0, 0, 1));
|
||||
|
||||
for (const auto& msg : CalCtx.messages) {
|
||||
if (msg.type == CalibrationContext::Message::String) {
|
||||
ImGui::TextWrapped("> %s", msg.str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ShowCalibrationDebug(1, 3);
|
||||
}
|
||||
|
||||
void BuildMenu(bool runningInOverlay)
|
||||
{
|
||||
auto &io = ImGui::GetIO();
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
ImGui::Text("");
|
||||
|
||||
if (CalCtx.state == CalibrationState::Continuous || CalCtx.state == CalibrationState::ContinuousStandby) {
|
||||
float width = ImGui::GetWindowContentRegionWidth(), scale = 1.0f;
|
||||
|
||||
if (ImGui::Button("Cancel Continuous Calibration", ImVec2(width * scale, ImGui::GetTextLineHeight() * 2))) {
|
||||
EndContinuousCalibration();
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Hide target device from application", &CalCtx.quashTargetInContinuous);
|
||||
|
||||
// Status field...
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0, 0, 0, 1));
|
||||
|
||||
for (const auto& msg : CalCtx.messages) {
|
||||
if (msg.type == CalibrationContext::Message::String) {
|
||||
ImGui::TextWrapped("> %s", msg.str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
else if (CalCtx.state == CalibrationState::None)
|
||||
if (CalCtx.state == CalibrationState::None)
|
||||
{
|
||||
if (CalCtx.validProfile && !CalCtx.enabled)
|
||||
{
|
||||
@@ -199,16 +553,6 @@ void BuildMenu(bool runningInOverlay)
|
||||
ImGui::Button("Calibration in progress...", ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetTextLineHeight() * 2));
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(10.0f, ImGui::GetWindowHeight() - ImGui::GetFrameHeightWithSpacing()));
|
||||
ImGui::BeginChild("bottom line", ImVec2(ImGui::GetWindowWidth() - 20.0f, ImGui::GetFrameHeightWithSpacing() * 2), false);
|
||||
ImGui::Text("OpenVR Space Calibrator v" SPACECAL_VERSION_STRING " - by tach/pushrax");
|
||||
if (runningInOverlay)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("- close VR overlay to use mouse");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x - 40.0f, io.DisplaySize.y - 40.0f), ImGuiCond_Always);
|
||||
if (ImGui::BeginPopupModal("Calibration Progress", nullptr, bareWindowFlags))
|
||||
@@ -426,6 +770,7 @@ void BuildDeviceSelection(const VRState &state, int &initialSelected, const std:
|
||||
}
|
||||
|
||||
if (selected == -1 && standby) {
|
||||
bool present = false;
|
||||
for (auto& device : state.devices)
|
||||
{
|
||||
if (device.trackingSystem != system)
|
||||
@@ -434,11 +779,11 @@ void BuildDeviceSelection(const VRState &state, int &initialSelected, const std:
|
||||
if (standbyDevice.model != device.model) continue;
|
||||
if (standbyDevice.serial != device.serial) continue;
|
||||
|
||||
selected = device.id;
|
||||
present = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (selected == -1) {
|
||||
if (!present) {
|
||||
auto label = LabelString(standbyDevice);
|
||||
ImGui::Selectable(label.c_str(), true);
|
||||
}
|
||||
@@ -491,62 +836,11 @@ void BuildDeviceSelections(const VRState &state)
|
||||
}
|
||||
}
|
||||
|
||||
VRState LoadVRState()
|
||||
{
|
||||
VRState state;
|
||||
auto &trackingSystems = state.trackingSystems;
|
||||
VRState LoadVRState() {
|
||||
VRState state = VRState::Load();
|
||||
auto& trackingSystems = state.trackingSystems;
|
||||
|
||||
char buffer[vr::k_unMaxPropertyStringSize];
|
||||
|
||||
for (uint32_t id = 0; id < vr::k_unMaxTrackedDeviceCount; ++id)
|
||||
{
|
||||
vr::ETrackedPropertyError err = vr::TrackedProp_Success;
|
||||
auto deviceClass = vr::VRSystem()->GetTrackedDeviceClass(id);
|
||||
if (deviceClass == vr::TrackedDeviceClass_Invalid)
|
||||
continue;
|
||||
|
||||
if (deviceClass != vr::TrackedDeviceClass_TrackingReference)
|
||||
{
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_TrackingSystemName_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
||||
|
||||
if (err == vr::TrackedProp_Success)
|
||||
{
|
||||
std::string system(buffer);
|
||||
auto existing = std::find(trackingSystems.begin(), trackingSystems.end(), system);
|
||||
if (existing != trackingSystems.end())
|
||||
{
|
||||
if (deviceClass == vr::TrackedDeviceClass_HMD)
|
||||
{
|
||||
trackingSystems.erase(existing);
|
||||
trackingSystems.insert(trackingSystems.begin(), system);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trackingSystems.push_back(system);
|
||||
}
|
||||
|
||||
VRDevice device;
|
||||
device.id = id;
|
||||
device.deviceClass = deviceClass;
|
||||
device.trackingSystem = system;
|
||||
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_ModelNumber_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
||||
device.model = std::string(buffer);
|
||||
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_SerialNumber_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
||||
device.serial = std::string(buffer);
|
||||
|
||||
device.controllerRole = (vr::ETrackedControllerRole) vr::VRSystem()->GetInt32TrackedDeviceProperty(id, vr::Prop_ControllerRoleHint_Int32, &err);
|
||||
|
||||
state.devices.push_back(device);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("failed to get tracking system name for id %d\n", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Inject entries for continuous calibration targets which have yet to load
|
||||
|
||||
if (CalCtx.state == CalibrationState::ContinuousStandby) {
|
||||
auto existing = std::find(trackingSystems.begin(), trackingSystems.end(), CalCtx.referenceTrackingSystem);
|
||||
@@ -606,3 +900,4 @@ void TextWithWidth(const char *label, const char *text, float width)
|
||||
ImGui::Text(text);
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void BuildMainWindow(bool runningInOverlay);
|
||||
void BuildMainWindow(bool runningInOverlay);
|
||||
void RequestImmediateRedraw();
|
||||
@@ -0,0 +1,108 @@
|
||||
#include "stdafx.h"
|
||||
#include "VRState.h"
|
||||
|
||||
VRState VRState::Load()
|
||||
{
|
||||
VRState state;
|
||||
auto& trackingSystems = state.trackingSystems;
|
||||
|
||||
char buffer[vr::k_unMaxPropertyStringSize];
|
||||
|
||||
for (uint32_t id = 0; id < vr::k_unMaxTrackedDeviceCount; ++id)
|
||||
{
|
||||
vr::ETrackedPropertyError err = vr::TrackedProp_Success;
|
||||
auto deviceClass = vr::VRSystem()->GetTrackedDeviceClass(id);
|
||||
if (deviceClass == vr::TrackedDeviceClass_Invalid)
|
||||
continue;
|
||||
|
||||
if (deviceClass != vr::TrackedDeviceClass_TrackingReference)
|
||||
{
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_TrackingSystemName_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
||||
|
||||
if (err == vr::TrackedProp_Success)
|
||||
{
|
||||
std::string system(buffer);
|
||||
|
||||
// Check if the current HMD is a Pimax crystal
|
||||
if (deviceClass == vr::TrackedDeviceClass_HMD && system == "aapvr") {
|
||||
// HMD is a Pimax HMD
|
||||
vr::HmdMatrix34_t eyeToHeadLeft = vr::VRSystem()->GetEyeToHeadTransform(vr::Eye_Left);
|
||||
// Crystal's projection matrix is constant 0s or 1s except for [0][3], which stores the IPD offset from the nose
|
||||
bool isCrystalHmd =
|
||||
eyeToHeadLeft.m[0][0] == 1 && eyeToHeadLeft.m[0][1] == 0 && eyeToHeadLeft.m[0][2] == 0 && // IPD
|
||||
eyeToHeadLeft.m[1][0] == 0 && eyeToHeadLeft.m[1][1] == 1 && eyeToHeadLeft.m[1][2] == 0 && eyeToHeadLeft.m[1][3] == 0 &&
|
||||
eyeToHeadLeft.m[2][0] == 0 && eyeToHeadLeft.m[2][1] == 0 && eyeToHeadLeft.m[2][2] == 1 && eyeToHeadLeft.m[2][3] == 0;
|
||||
|
||||
if (isCrystalHmd) {
|
||||
// Move it outside the aapvr system ; we treat aapvr as if it were lighthouse
|
||||
system = "Pimax Crystal HMD";
|
||||
}
|
||||
} else if (deviceClass == vr::TrackedDeviceClass_Controller && system == "oculus") {
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_RenderModelName_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
||||
std::string renderModel(buffer);
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_ConnectedWirelessDongle_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
||||
std::string connectedWirelessDongle(buffer);
|
||||
|
||||
// Check if the controller claims its an oculus controller but also pimax
|
||||
if (renderModel.find("{aapvr}") != std::string::npos &&
|
||||
renderModel.find("crystal") != std::string::npos &&
|
||||
connectedWirelessDongle.find("lighthouse") != std::string::npos) {
|
||||
system = "Pimax Crystal Controllers";
|
||||
}
|
||||
}
|
||||
|
||||
auto existing = std::find(trackingSystems.begin(), trackingSystems.end(), system);
|
||||
if (existing != trackingSystems.end())
|
||||
{
|
||||
if (deviceClass == vr::TrackedDeviceClass_HMD)
|
||||
{
|
||||
trackingSystems.erase(existing);
|
||||
trackingSystems.insert(trackingSystems.begin(), system);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trackingSystems.push_back(system);
|
||||
}
|
||||
|
||||
VRDevice device;
|
||||
device.id = id;
|
||||
device.deviceClass = deviceClass;
|
||||
device.trackingSystem = system;
|
||||
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_ModelNumber_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
||||
device.model = std::string(buffer);
|
||||
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_SerialNumber_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
||||
device.serial = std::string(buffer);
|
||||
|
||||
device.controllerRole = (vr::ETrackedControllerRole)vr::VRSystem()->GetInt32TrackedDeviceProperty(id, vr::Prop_ControllerRoleHint_Int32, &err);
|
||||
|
||||
state.devices.push_back(device);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("failed to get tracking system name for id %d\n", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
int VRState::FindDevice(const std::string& trackingSystem, const std::string& model, const std::string& serial) const {
|
||||
for (int i = 0; i < devices.size(); i++) {
|
||||
const auto& device = devices[i];
|
||||
|
||||
if (device.trackingSystem == trackingSystem && device.model == model && device.serial == serial) return device.id;
|
||||
}
|
||||
|
||||
// allow fallback excluding serial for HMD specifically
|
||||
for (int i = 0; i < devices.size(); i++) {
|
||||
const auto& device = devices[i];
|
||||
|
||||
if (device.trackingSystem == trackingSystem && device.model == model && device.deviceClass == vr::TrackedDeviceClass::TrackedDeviceClass_HMD) return device.id;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <openvr.h>
|
||||
|
||||
struct VRDevice
|
||||
{
|
||||
int id = -1;
|
||||
vr::TrackedDeviceClass deviceClass;
|
||||
std::string model = "";
|
||||
std::string serial = "";
|
||||
std::string trackingSystem = "";
|
||||
vr::ETrackedControllerRole controllerRole = vr::TrackedControllerRole_Invalid;
|
||||
};
|
||||
|
||||
struct VRState
|
||||
{
|
||||
std::vector<std::string> trackingSystems;
|
||||
std::vector<VRDevice> devices;
|
||||
|
||||
int FindDevice(const std::string& trackingSystem, const std::string& model, const std::string& serial) const;
|
||||
|
||||
static VRState Load();
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
#include "imgui_extensions.h"
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include "imgui_internal.h"
|
||||
|
||||
static ImVector<ImRect> s_GroupPanelLabelStack;
|
||||
|
||||
void ImGui::BeginGroupPanel(const char* name, const ImVec2& size)
|
||||
{
|
||||
ImGui::BeginGroup();
|
||||
|
||||
auto cursorPos = ImGui::GetCursorScreenPos();
|
||||
auto itemSpacing = ImGui::GetStyle().ItemSpacing;
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
|
||||
|
||||
auto frameHeight = ImGui::GetFrameHeight();
|
||||
ImGui::BeginGroup();
|
||||
|
||||
ImVec2 effectiveSize = size;
|
||||
if (size.x < 0.0f)
|
||||
effectiveSize.x = ImGui::GetContentRegionAvail().x;
|
||||
else
|
||||
effectiveSize.x = size.x;
|
||||
ImGui::Dummy(ImVec2(effectiveSize.x, 0.0f));
|
||||
|
||||
ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
|
||||
ImGui::SameLine(0.0f, 0.0f);
|
||||
ImGui::BeginGroup();
|
||||
ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
|
||||
ImGui::SameLine(0.0f, 0.0f);
|
||||
ImGui::TextUnformatted(name);
|
||||
auto labelMin = ImGui::GetItemRectMin();
|
||||
auto labelMax = ImGui::GetItemRectMax();
|
||||
ImGui::SameLine(0.0f, 0.0f);
|
||||
ImGui::Dummy(ImVec2(0.0, frameHeight + itemSpacing.y));
|
||||
ImGui::BeginGroup();
|
||||
|
||||
//ImGui::GetWindowDrawList()->AddRect(labelMin, labelMax, IM_COL32(255, 0, 255, 255));
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
#if IMGUI_VERSION_NUM >= 17301
|
||||
ImGui::GetCurrentWindow()->ContentRegionRect.Max.x -= frameHeight * 0.5f;
|
||||
ImGui::GetCurrentWindow()->WorkRect.Max.x -= frameHeight * 0.5f;
|
||||
ImGui::GetCurrentWindow()->InnerRect.Max.x -= frameHeight * 0.5f;
|
||||
#else
|
||||
ImGui::GetCurrentWindow()->ContentsRegionRect.Max.x -= frameHeight * 0.5f;
|
||||
#endif
|
||||
ImGui::GetCurrentWindow()->Size.x -= frameHeight;
|
||||
|
||||
auto itemWidth = ImGui::CalcItemWidth();
|
||||
ImGui::PushItemWidth(ImMax(0.0f, itemWidth - frameHeight));
|
||||
|
||||
s_GroupPanelLabelStack.push_back(ImRect(labelMin, labelMax));
|
||||
}
|
||||
|
||||
void ImGui::EndGroupPanel()
|
||||
{
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
auto itemSpacing = ImGui::GetStyle().ItemSpacing;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
|
||||
|
||||
auto frameHeight = ImGui::GetFrameHeight();
|
||||
|
||||
ImGui::EndGroup();
|
||||
|
||||
//ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(0, 255, 0, 64), 4.0f);
|
||||
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::SameLine(0.0f, 0.0f);
|
||||
ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
|
||||
ImGui::Dummy(ImVec2(0.0, frameHeight - frameHeight * 0.5f - itemSpacing.y));
|
||||
|
||||
ImGui::EndGroup();
|
||||
|
||||
auto itemMin = ImGui::GetItemRectMin();
|
||||
auto itemMax = ImGui::GetItemRectMax();
|
||||
//ImGui::GetWindowDrawList()->AddRectFilled(itemMin, itemMax, IM_COL32(255, 0, 0, 64), 4.0f);
|
||||
|
||||
auto labelRect = s_GroupPanelLabelStack.back();
|
||||
s_GroupPanelLabelStack.pop_back();
|
||||
|
||||
ImVec2 halfFrame = ImVec2(frameHeight * 0.25f, frameHeight) * 0.5f;
|
||||
ImRect frameRect = ImRect(itemMin + halfFrame, itemMax - ImVec2(halfFrame.x, 0.0f));
|
||||
labelRect.Min.x -= itemSpacing.x;
|
||||
labelRect.Max.x += itemSpacing.x;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
// left half-plane
|
||||
case 0: ImGui::PushClipRect(ImVec2(-FLT_MAX, -FLT_MAX), ImVec2(labelRect.Min.x, FLT_MAX), true); break;
|
||||
// right half-plane
|
||||
case 1: ImGui::PushClipRect(ImVec2(labelRect.Max.x, -FLT_MAX), ImVec2(FLT_MAX, FLT_MAX), true); break;
|
||||
// top
|
||||
case 2: ImGui::PushClipRect(ImVec2(labelRect.Min.x, -FLT_MAX), ImVec2(labelRect.Max.x, labelRect.Min.y), true); break;
|
||||
// bottom
|
||||
case 3: ImGui::PushClipRect(ImVec2(labelRect.Min.x, labelRect.Max.y), ImVec2(labelRect.Max.x, FLT_MAX), true); break;
|
||||
}
|
||||
|
||||
ImGui::GetWindowDrawList()->AddRect(
|
||||
frameRect.Min, frameRect.Max,
|
||||
ImColor(ImGui::GetStyleColorVec4(ImGuiCol_Border)),
|
||||
halfFrame.x);
|
||||
|
||||
ImGui::PopClipRect();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
#if IMGUI_VERSION_NUM >= 17301
|
||||
ImGui::GetCurrentWindow()->ContentRegionRect.Max.x += frameHeight * 0.5f;
|
||||
ImGui::GetCurrentWindow()->WorkRect.Max.x += frameHeight * 0.5f;
|
||||
ImGui::GetCurrentWindow()->InnerRect.Max.x += frameHeight * 0.5f;
|
||||
#else
|
||||
ImGui::GetCurrentWindow()->ContentsRegionRect.Max.x += frameHeight * 0.5f;
|
||||
#endif
|
||||
ImGui::GetCurrentWindow()->Size.x += frameHeight;
|
||||
|
||||
ImGui::Dummy(ImVec2(0.0f, 0.0f));
|
||||
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace ImGui
|
||||
{
|
||||
// Group panels taken from https://github.com/ocornut/imgui/issues/1496#issuecomment-655048353
|
||||
// Licensed under CC0 license as per https://github.com/ocornut/imgui/issues/1496#issuecomment-1287772456
|
||||
|
||||
void BeginGroupPanel(const char* name, const ImVec2& size = ImVec2(0.0f, 0.0f));
|
||||
void EndGroupPanel();
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#define EIGEN_MPL2_ONLY
|
||||
|
||||
#include "targetver.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
@@ -16,6 +16,16 @@ void IPCServer::HandleRequest(const protocol::Request &request, protocol::Respon
|
||||
response.type = protocol::ResponseSuccess;
|
||||
break;
|
||||
|
||||
case protocol::RequestDebugOffset:
|
||||
driver->HandleApplyRandomOffset();
|
||||
response.type = protocol::ResponseSuccess;
|
||||
break;
|
||||
|
||||
case protocol::RequestSetAlignmentSpeedParams:
|
||||
driver->HandleSetAlignmentSpeedParams(request.setAlignmentSpeedParams);
|
||||
response.type = protocol::ResponseSuccess;
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG("Invalid IPC request: %d", request.type);
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#define EIGEN_MPL2_ONLY
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
/**
|
||||
* Contains an isometric transformation, represented as the pair of a rotation quaternion and translation vector.
|
||||
* The translation is applied to the left of the quaternion.
|
||||
*/
|
||||
struct IsoTransform {
|
||||
Eigen::Quaterniond rotation;
|
||||
Eigen::Vector3d translation;
|
||||
|
||||
IsoTransform() : rotation(Eigen::Quaterniond::Identity()), translation(Eigen::Vector3d::Zero()) {}
|
||||
IsoTransform(const Eigen::Quaterniond &rot) : rotation(rot), translation(Eigen::Vector3d::Zero()) {}
|
||||
IsoTransform(const Eigen::Vector3d &trans) : rotation(Eigen::Quaterniond::Identity()) {}
|
||||
IsoTransform(const Eigen::Quaterniond& rot, const Eigen::Vector3d& trans) : rotation(rot), translation(trans) {}
|
||||
|
||||
void pretranslate(const Eigen::Vector3d& t) {
|
||||
translation += t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates between this transform and target. The position of localPoint after transformation will smoothly
|
||||
* lerp between (this * localPoint) and (target * localPoint), despite rotation occurring around it.
|
||||
*/
|
||||
IsoTransform interpolateAround(double lerp, const IsoTransform& target, const Eigen::Vector3d& localPoint) const;
|
||||
};
|
||||
|
||||
inline IsoTransform operator*(const IsoTransform& a, const IsoTransform& b) {
|
||||
// tA * rA * tB * rB = tA * (trans(rA * tB)) * rA * rB
|
||||
auto rot = a.rotation * b.rotation;
|
||||
Eigen::Vector3d trans = a.translation + Eigen::Isometry3d(a.rotation) * b.translation;
|
||||
|
||||
return IsoTransform(rot, trans);
|
||||
}
|
||||
|
||||
inline Eigen::Vector3d operator*(const IsoTransform& a, const Eigen::Vector3d& p) {
|
||||
return a.translation + Eigen::Isometry3d(a.rotation) * p;
|
||||
}
|
||||
|
||||
inline IsoTransform IsoTransform::interpolateAround(double lerp, const IsoTransform& target, const Eigen::Vector3d& localPoint) const {
|
||||
auto initialPos = (*this) * localPoint;
|
||||
Eigen::Vector3d finalPos = initialPos * (1 - lerp) + (target * localPoint) * lerp;
|
||||
|
||||
auto newRotation = rotation.slerp(lerp, target.rotation);
|
||||
Eigen::Vector3d newTranslation = finalPos - Eigen::Isometry3d(newRotation) * localPoint;
|
||||
|
||||
return IsoTransform(newRotation, newTranslation);
|
||||
}
|
||||
@@ -64,7 +64,7 @@
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>..\lib\MinHook\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>libMinHook.x64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
@@ -85,7 +85,7 @@
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>..\lib\MinHook\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>libMinHook.x64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
@@ -93,6 +93,7 @@
|
||||
<ClInclude Include="Hooking.h" />
|
||||
<ClInclude Include="InterfaceHookInjector.h" />
|
||||
<ClInclude Include="IPCServer.h" />
|
||||
<ClInclude Include="IsometryTransform.h" />
|
||||
<ClInclude Include="Logging.h" />
|
||||
<ClInclude Include="OpenVR-SpaceCalibratorDriver.h" />
|
||||
<ClInclude Include="ServerTrackedDeviceProvider.h" />
|
||||
@@ -114,6 +115,11 @@
|
||||
<ClCompile Include="OpenVR-SpaceCalibratorDriver.cpp" />
|
||||
<ClCompile Include="ServerTrackedDeviceProvider.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\lib\minhook\build\VC16\libMinHook.vcxproj">
|
||||
<Project>{f142a341-5ee0-442d-a15f-98ae9b48dbae}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
<ClInclude Include="InterfaceHookInjector.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IsometryTransform.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="OpenVR-SpaceCalibratorDriver.cpp">
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "ServerTrackedDeviceProvider.h"
|
||||
#include "Logging.h"
|
||||
#include "InterfaceHookInjector.h"
|
||||
#include "IsometryTransform.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
vr::EVRInitError ServerTrackedDeviceProvider::Init(vr::IVRDriverContext *pDriverContext)
|
||||
{
|
||||
@@ -8,11 +11,27 @@ vr::EVRInitError ServerTrackedDeviceProvider::Init(vr::IVRDriverContext *pDriver
|
||||
VR_INIT_SERVER_DRIVER_CONTEXT(pDriverContext);
|
||||
|
||||
memset(transforms, 0, vr::k_unMaxTrackedDeviceCount * sizeof DeviceTransform);
|
||||
memset(&alignmentSpeedParams, 0, sizeof alignmentSpeedParams);
|
||||
|
||||
alignmentSpeedParams.thr_rot_tiny = 0.1f * (EIGEN_PI / 180.0f);
|
||||
alignmentSpeedParams.thr_rot_small = 1.0f * (EIGEN_PI / 180.0f);
|
||||
alignmentSpeedParams.thr_rot_large = 5.0f * (EIGEN_PI / 180.0f);
|
||||
|
||||
alignmentSpeedParams.thr_trans_tiny = 0.1f / 1000.0; // mm
|
||||
alignmentSpeedParams.thr_trans_small = 1.0f / 1000.0; // mm
|
||||
alignmentSpeedParams.thr_trans_large = 20.0f / 1000.0; // mm
|
||||
|
||||
alignmentSpeedParams.align_speed_tiny = 0.05f;
|
||||
alignmentSpeedParams.align_speed_small = 0.2f;
|
||||
alignmentSpeedParams.align_speed_large = 2.0f;
|
||||
|
||||
InjectHooks(this, pDriverContext);
|
||||
server.Run();
|
||||
shmem.Create(OPENVR_SPACECALIBRATOR_SHMEM_NAME);
|
||||
|
||||
debugTransform = Eigen::Vector3d::Zero();
|
||||
debugRotation = Eigen::Quaterniond::Identity();
|
||||
|
||||
return vr::VRInitError_None;
|
||||
}
|
||||
|
||||
@@ -26,6 +45,8 @@ void ServerTrackedDeviceProvider::Cleanup()
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
vr::HmdQuaternion_t convert(const Eigen::Quaterniond& q) {
|
||||
vr::HmdQuaternion_t result;
|
||||
result.w = q.w();
|
||||
@@ -37,9 +58,9 @@ namespace {
|
||||
|
||||
vr::HmdVector3_t convert(const Eigen::Vector3d& v) {
|
||||
vr::HmdVector3_t result;
|
||||
result.v[0] = v.x();
|
||||
result.v[1] = v.y();
|
||||
result.v[2] = v.z();
|
||||
result.v[0] = (float) v.x();
|
||||
result.v[1] = (float) v.y();
|
||||
result.v[2] = (float) v.z();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -54,8 +75,93 @@ namespace {
|
||||
Eigen::Vector3d convert(const double* arr) {
|
||||
return Eigen::Vector3d(arr[0], arr[1], arr[2]);
|
||||
}
|
||||
|
||||
IsoTransform toIsoWorldTransform(const vr::DriverPose_t& pose) {
|
||||
Eigen::Quaterniond rot(pose.qWorldFromDriverRotation.w, pose.qWorldFromDriverRotation.x, pose.qWorldFromDriverRotation.y, pose.qWorldFromDriverRotation.z);
|
||||
Eigen::Vector3d trans(pose.vecWorldFromDriverTranslation[0], pose.vecWorldFromDriverTranslation[1], pose.vecWorldFromDriverTranslation[2]);
|
||||
|
||||
return IsoTransform(rot, trans);
|
||||
}
|
||||
|
||||
IsoTransform toIsoPose(const vr::DriverPose_t& pose) {
|
||||
auto worldXform = toIsoWorldTransform(pose);
|
||||
|
||||
Eigen::Quaterniond rot(pose.qRotation.w, pose.qRotation.x, pose.qRotation.y, pose.qRotation.z);
|
||||
Eigen::Vector3d trans(pose.vecPosition[0], pose.vecPosition[1], pose.vecPosition[2]);
|
||||
|
||||
return worldXform * IsoTransform(rot, trans);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function heuristically evaluates the amount of drift between the src and target playspace transforms,
|
||||
* evaluated centered on the `pose` device transform. This is then used to control the speed of realignment.
|
||||
*/
|
||||
ServerTrackedDeviceProvider::DeltaSize ServerTrackedDeviceProvider::GetTransformDeltaSize(
|
||||
DeltaSize prior_delta,
|
||||
const IsoTransform& deviceWorldPose,
|
||||
const IsoTransform& src,
|
||||
const IsoTransform& target
|
||||
) const {
|
||||
const auto src_pose = src * deviceWorldPose;
|
||||
const auto target_pose = target * deviceWorldPose;
|
||||
|
||||
const auto trans_delta = (src_pose.translation - target_pose.translation).squaredNorm();
|
||||
const auto rot_delta = src_pose.rotation.angularDistance(target_pose.rotation);
|
||||
|
||||
DeltaSize trans_level, rot_level;
|
||||
|
||||
if (trans_delta > alignmentSpeedParams.thr_trans_large) trans_level = DeltaSize::LARGE;
|
||||
else if (trans_delta > alignmentSpeedParams.thr_trans_small) trans_level = DeltaSize::SMALL;
|
||||
else trans_level = DeltaSize::TINY;
|
||||
|
||||
if (rot_delta > alignmentSpeedParams.thr_rot_large) rot_level = DeltaSize::LARGE;
|
||||
else if (rot_delta > alignmentSpeedParams.thr_rot_small) rot_level = DeltaSize::SMALL;
|
||||
else rot_level = DeltaSize::TINY;
|
||||
|
||||
if (trans_level == DeltaSize::TINY && rot_level == DeltaSize::TINY) return DeltaSize::TINY;
|
||||
else return max(prior_delta, max(trans_level, rot_level));
|
||||
}
|
||||
|
||||
double ServerTrackedDeviceProvider::GetTransformRate(DeltaSize delta) const {
|
||||
switch (delta) {
|
||||
case DeltaSize::TINY: return alignmentSpeedParams.align_speed_tiny;
|
||||
case DeltaSize::SMALL: return alignmentSpeedParams.align_speed_small;
|
||||
default: return alignmentSpeedParams.align_speed_large;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Smoothly interpolates the device active transform towards the target transform.
|
||||
*/
|
||||
void ServerTrackedDeviceProvider::BlendTransform(DeviceTransform& device, const IsoTransform &deviceWorldPose) const {
|
||||
LARGE_INTEGER timestamp, freq;
|
||||
QueryPerformanceCounter(×tamp);
|
||||
QueryPerformanceFrequency(&freq);
|
||||
|
||||
double lerp = (timestamp.QuadPart - device.lastPoll.QuadPart) / (double)freq.QuadPart;
|
||||
device.lastPoll = timestamp;
|
||||
|
||||
lerp *= GetTransformRate(device.currentRate);
|
||||
if (lerp > 1.0)
|
||||
lerp = 1.0;
|
||||
if (lerp < 0 || isnan(lerp))
|
||||
lerp = 0;
|
||||
|
||||
device.transform = device.transform.interpolateAround(lerp, device.targetTransform, deviceWorldPose.translation);
|
||||
}
|
||||
|
||||
void ServerTrackedDeviceProvider::ApplyTransform(DeviceTransform& device, vr::DriverPose_t& devicePose) const {
|
||||
auto deviceWorldTransform = toIsoWorldTransform(devicePose);
|
||||
deviceWorldTransform = device.transform * deviceWorldTransform;
|
||||
devicePose.vecWorldFromDriverTranslation[0] = deviceWorldTransform.translation(0);
|
||||
devicePose.vecWorldFromDriverTranslation[1] = deviceWorldTransform.translation(1);
|
||||
devicePose.vecWorldFromDriverTranslation[2] = deviceWorldTransform.translation(2);
|
||||
devicePose.qWorldFromDriverRotation = convert(deviceWorldTransform.rotation);
|
||||
}
|
||||
|
||||
|
||||
inline vr::HmdQuaternion_t operator*(const vr::HmdQuaternion_t &lhs, const vr::HmdQuaternion_t &rhs) {
|
||||
return {
|
||||
(lhs.w * rhs.w) - (lhs.x * rhs.x) - (lhs.y * rhs.y) - (lhs.z * rhs.z),
|
||||
@@ -78,17 +184,17 @@ void ServerTrackedDeviceProvider::SetDeviceTransform(const protocol::SetDeviceTr
|
||||
tf.enabled = newTransform.enabled;
|
||||
|
||||
if (newTransform.updateTranslation) {
|
||||
tf.targetTranslation = convert(newTransform.translation);
|
||||
tf.targetTransform.translation = convert(newTransform.translation);
|
||||
if (!newTransform.lerp) {
|
||||
tf.translation = tf.targetTranslation;
|
||||
tf.transform.translation = tf.targetTransform.translation;
|
||||
}
|
||||
}
|
||||
|
||||
if (newTransform.updateRotation) {
|
||||
tf.targetRotation = convert(newTransform.rotation);
|
||||
tf.targetTransform.rotation = convert(newTransform.rotation);
|
||||
|
||||
if (!newTransform.lerp) {
|
||||
tf.rotation = tf.targetRotation;
|
||||
tf.transform.rotation = tf.targetTransform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +206,16 @@ void ServerTrackedDeviceProvider::SetDeviceTransform(const protocol::SetDeviceTr
|
||||
|
||||
bool ServerTrackedDeviceProvider::HandleDevicePoseUpdated(uint32_t openVRID, vr::DriverPose_t &pose)
|
||||
{
|
||||
// Apply debug pose before anything else
|
||||
if (openVRID > 0) {
|
||||
auto dbgPos = convert(pose.vecPosition) + debugTransform;
|
||||
auto dbgRot = convert(pose.qRotation) * debugRotation;
|
||||
pose.qRotation = convert(dbgRot);
|
||||
pose.vecPosition[0] = dbgPos(0);
|
||||
pose.vecPosition[1] = dbgPos(1);
|
||||
pose.vecPosition[2] = dbgPos(2);
|
||||
}
|
||||
|
||||
shmem.SetPose(openVRID, pose);
|
||||
|
||||
auto& tf = transforms[openVRID];
|
||||
@@ -110,39 +226,32 @@ bool ServerTrackedDeviceProvider::HandleDevicePoseUpdated(uint32_t openVRID, vr:
|
||||
pose.vecPosition[2] = -pose.vecWorldFromDriverTranslation[2];
|
||||
} else if (tf.enabled)
|
||||
{
|
||||
LARGE_INTEGER timestamp, freq;
|
||||
QueryPerformanceCounter(×tamp);
|
||||
QueryPerformanceFrequency(&freq);
|
||||
|
||||
double lerp = (timestamp.QuadPart - tf.lastPoll.QuadPart) / (double)freq.QuadPart;
|
||||
lerp *= 2;
|
||||
if (lerp > 1.0)
|
||||
lerp = 1.0;
|
||||
if (lerp < 0 || isnan(lerp))
|
||||
lerp = 0;
|
||||
// Cancel out any translation induced by this rotation slerp. This helps avoid your
|
||||
// controllers/trackers flying away when far from the origin and a rotation correction
|
||||
// is performed.
|
||||
auto priorPos = tf.rotation * convert(pose.vecPosition);
|
||||
tf.rotation = tf.rotation.slerp(lerp, tf.targetRotation);
|
||||
auto newPos = tf.rotation * convert(pose.vecPosition);
|
||||
tf.translation -= newPos - priorPos;
|
||||
|
||||
tf.translation = tf.translation * (1 - lerp) + tf.targetTranslation * lerp;
|
||||
tf.lastPoll = timestamp;
|
||||
|
||||
pose.qWorldFromDriverRotation = convert(tf.rotation * convert(pose.qWorldFromDriverRotation));
|
||||
|
||||
// @TODO: Offset, scale, and re-offset
|
||||
pose.vecPosition[0] *= tf.scale;
|
||||
pose.vecPosition[1] *= tf.scale;
|
||||
pose.vecPosition[2] *= tf.scale;
|
||||
|
||||
auto newTranslation = tf.translation + tf.rotation * convert(pose.vecWorldFromDriverTranslation);
|
||||
pose.vecWorldFromDriverTranslation[0] = newTranslation.x();
|
||||
pose.vecWorldFromDriverTranslation[1] = newTranslation.y();
|
||||
pose.vecWorldFromDriverTranslation[2] = newTranslation.z();
|
||||
auto deviceWorldPose = toIsoPose(pose);
|
||||
tf.currentRate = GetTransformDeltaSize(tf.currentRate, deviceWorldPose, tf.transform, tf.targetTransform);
|
||||
double lerp = GetTransformRate(tf.currentRate);
|
||||
|
||||
BlendTransform(tf, deviceWorldPose);
|
||||
ApplyTransform(tf, pose);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ServerTrackedDeviceProvider::HandleApplyRandomOffset() {
|
||||
std::random_device gen;
|
||||
std::uniform_real_distribution<double> d(-1, 1);
|
||||
auto init = Eigen::Vector3d(d(gen), d(gen), d(gen));
|
||||
auto posOffset = init * 0.25f;
|
||||
|
||||
debugTransform = posOffset;
|
||||
debugRotation = Eigen::Quaterniond::Identity();
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "Applied random offset: " << posOffset << " from init " << init << std::endl;
|
||||
LOG("%s", oss.str().c_str());
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#define EIGEN_MPL2_ONLY
|
||||
|
||||
#include "IPCServer.h"
|
||||
#include "../Protocol.h"
|
||||
#include "IsometryTransform.h"
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
#include <openvr_driver.h>
|
||||
|
||||
|
||||
class ServerTrackedDeviceProvider : public vr::IServerTrackedDeviceProvider
|
||||
{
|
||||
public:
|
||||
@@ -40,20 +44,48 @@ public:
|
||||
ServerTrackedDeviceProvider() : server(this) { }
|
||||
void SetDeviceTransform(const protocol::SetDeviceTransform &newTransform);
|
||||
bool HandleDevicePoseUpdated(uint32_t openVRID, vr::DriverPose_t &pose);
|
||||
void HandleApplyRandomOffset();
|
||||
void HandleSetAlignmentSpeedParams(const protocol::AlignmentSpeedParams params) {
|
||||
alignmentSpeedParams = params;
|
||||
}
|
||||
|
||||
private:
|
||||
IPCServer server;
|
||||
protocol::DriverPoseShmem shmem;
|
||||
|
||||
enum DeltaSize {
|
||||
TINY,
|
||||
SMALL,
|
||||
LARGE
|
||||
};
|
||||
|
||||
struct DeviceTransform
|
||||
{
|
||||
bool enabled = false;
|
||||
bool quash = false;
|
||||
Eigen::Vector3d translation, targetTranslation;
|
||||
Eigen::Quaterniond rotation, targetRotation;
|
||||
IsoTransform transform, targetTransform;
|
||||
double scale;
|
||||
LARGE_INTEGER lastPoll;
|
||||
DeltaSize currentRate = DeltaSize::TINY;
|
||||
};
|
||||
|
||||
DeviceTransform transforms[vr::k_unMaxTrackedDeviceCount];
|
||||
Eigen::Vector3d debugTransform;
|
||||
Eigen::Quaterniond debugRotation;
|
||||
|
||||
DeltaSize currentDeltaSpeed[vr::k_unMaxTrackedDeviceCount];
|
||||
|
||||
protocol::AlignmentSpeedParams alignmentSpeedParams;
|
||||
|
||||
DeltaSize GetTransformDeltaSize(
|
||||
DeltaSize prior_delta,
|
||||
const IsoTransform& deviceWorldPose,
|
||||
const IsoTransform& src,
|
||||
const IsoTransform& target
|
||||
) const;
|
||||
|
||||
double GetTransformRate(DeltaSize delta) const;
|
||||
|
||||
void BlendTransform(DeviceTransform& device, const IsoTransform& deviceWorldPose) const;
|
||||
void ApplyTransform(DeviceTransform& device, vr::DriverPose_t& devicePose) const;
|
||||
};
|
||||
+28
-1
@@ -92,13 +92,15 @@ namespace vr {
|
||||
|
||||
namespace protocol
|
||||
{
|
||||
const uint32_t Version = 3;
|
||||
const uint32_t Version = 4;
|
||||
|
||||
enum RequestType
|
||||
{
|
||||
RequestInvalid,
|
||||
RequestHandshake,
|
||||
RequestSetDeviceTransform,
|
||||
RequestSetAlignmentSpeedParams,
|
||||
RequestDebugOffset
|
||||
};
|
||||
|
||||
enum ResponseType
|
||||
@@ -113,6 +115,29 @@ namespace protocol
|
||||
uint32_t version = Version;
|
||||
};
|
||||
|
||||
struct AlignmentSpeedParams
|
||||
{
|
||||
/**
|
||||
* The threshold at which we adjust the alignment speed based on the position offset
|
||||
* between current and target calibrations. Generally, we increase the speed if we go
|
||||
* above small/large, and decrease it only once it's under tiny.
|
||||
*
|
||||
* These values are expressed as distance squared
|
||||
*/
|
||||
double thr_trans_tiny, thr_trans_small, thr_trans_large;
|
||||
|
||||
/**
|
||||
* Similar thresholds for rotation offsets, in radians
|
||||
*/
|
||||
double thr_rot_tiny, thr_rot_small, thr_rot_large;
|
||||
|
||||
/**
|
||||
* The speed of alignment, expressed as a lerp/slerp factor. 1 will blend most of the way in <1 second.
|
||||
* (We actually do a lerp(s * delta_t) where s is the speed factor here)
|
||||
*/
|
||||
double align_speed_tiny, align_speed_small, align_speed_large;
|
||||
};
|
||||
|
||||
struct SetDeviceTransform
|
||||
{
|
||||
uint32_t openVRID;
|
||||
@@ -151,10 +176,12 @@ namespace protocol
|
||||
|
||||
union {
|
||||
SetDeviceTransform setDeviceTransform;
|
||||
AlignmentSpeedParams setAlignmentSpeedParams;
|
||||
};
|
||||
|
||||
Request() : type(RequestInvalid) { }
|
||||
Request(RequestType type) : type(type) { }
|
||||
Request(AlignmentSpeedParams params) : type(RequestType::RequestSetAlignmentSpeedParams), setAlignmentSpeedParams(params) {}
|
||||
};
|
||||
|
||||
struct Response
|
||||
|
||||
@@ -13,7 +13,7 @@ A quick video of how this works using an old version (~v0.3) is available at htt
|
||||
|
||||
### Install
|
||||
|
||||
Before following the directions below, download and run the installer for the [latest release](https://github.com/pushrax/OpenVR-SpaceCalibrator/releases). This will automatically set up SteamVR for use with multiple tracking systems (`activateMultipleDrivers: true`). There are many guides that say you need to edit the SteamVR config manually. You do not.
|
||||
Before following the directions below, download and run the installer for the [latest release](https://github.com/hyblocker/OpenVR-SpaceCalibrator/releases/latest). This will automatically set up SteamVR for use with multiple tracking systems (`activateMultipleDrivers: true`). There are many guides that say you need to edit the SteamVR config manually. You do not.
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -21,22 +21,46 @@ Once Space Calibrator has a calibration, it works in the background to keep your
|
||||
|
||||
### Calibration
|
||||
|
||||
This fork supports continuous calibration. This means that there are two ways to calibrate:
|
||||
|
||||
#### 1. Normal Calibration
|
||||
|
||||
> Use this if you do not have a tracker on your VR headset.
|
||||
|
||||
As part of first time setup, or when you make a change to your space (e.g. move a sensor), and occasionally as the calibration drifts over time (consumer VR tracking isn't perfectly stable), you'll need to run a calibration:
|
||||
|
||||
1. Copy the chaperone/guardian bounds from your HMD's play space. This doesn't need to be run if your HMD's play space hasn't changed since last time you copied it. __Example:__ if you're using the Rift with Vive trackers and you bump a Vive lighthouse, or if the calibration has just drifted a little, you likely don't need to run this step, but if you bump an Oculus sensor you will (after running Oculus guardian setup again).
|
||||
1. Run SteamVR, with only devices from your HMD's tracking system powered on. __Example:__ for Rift with Vive trackers, don't turn on the trackers yet.
|
||||
2. Confirm your chaperone/guardian is set up with the walls in the right place. If you change it later, you need to run step again.
|
||||
3. Open SPACE CAL in the SteamVR dashboard overlay.
|
||||
4. Click `Copy Chaperone Bounds to profile`
|
||||
|
||||
1. Run SteamVR, with only devices from your HMD's tracking system powered on. __Example:__ for Rift with Vive trackers, don't turn on the trackers yet.
|
||||
2. Confirm your chaperone/guardian is set up with the walls in the right place. If you change it later, you need to run step again.
|
||||
3. Open SPACE CAL in the SteamVR dashboard overlay.
|
||||
4. Click `Copy Chaperone Bounds to profile`
|
||||
|
||||
2. Calibrate devices.
|
||||
1. Open SteamVR if you haven't already. Turn on some or all your devices.
|
||||
2. Open SPACE CAL in the SteamVR dashboard overlay.
|
||||
3. Select one device from the reference space on the left and one device from the target space on the right. If you turned on multiple devices from one space and can't tell which one is selected, click "Identify selected devices" to blink an LED or vibrate it. __Example:__ for Rift with Vive trackers, you'll see the Touch controllers on the left, and Vive trackers on the right. __Pro tip:__ if you turn on just one Vive tracker, you don't have to figure out which one is selected.
|
||||
4. Hold these two devices in one hand, like they're glued together. If they slip, calibration won't work as well.
|
||||
5. Click `Start Calibration`
|
||||
6. Move and rotate your hand around slowly a few times, like you're calibrating the compass on your phone. You want to sample as many orientations as possible.
|
||||
7. Done! A profile will be saved automatically. If you haven't already, turn on all your devices. Space Calibrator will automatically apply the calibration to devices as they turn on.
|
||||
|
||||
1. Open SteamVR if you haven't already. Turn on some or all your devices.
|
||||
2. Open SPACE CAL in the SteamVR dashboard overlay.
|
||||
3. Select one device from the reference space on the left and one device from the target space on the right. If you turned on multiple devices from one space and can't tell which one is selected, click "Identify selected devices" to blink an LED or vibrate it. __Example:__ for Rift with Vive trackers, you'll see the Touch controllers on the left, and Vive trackers on the right. __Pro tip:__ if you turn on just one Vive tracker, you don't have to figure out which one is selected.
|
||||
4. Hold these two devices in one hand, like they're glued together. If they slip, calibration won't work as well.
|
||||
5. Click `Start Calibration`
|
||||
6. Move and rotate your hand around slowly a few times, like you're calibrating the compass on your phone. You want to sample as many orientations as possible.
|
||||
7. Done! A profile will be saved automatically. If you haven't already, turn on all your devices. Space Calibrator will automatically apply the calibration to devices as they turn on.
|
||||
|
||||
#### 2. Continuous Calibration
|
||||
|
||||
> Use this only if you have a tracker on your VR headset.
|
||||
|
||||
Continuous calibration allows you to calibrate playspaces continuously, eliminating the need to do any of the work above.
|
||||
|
||||
1. Start SteamVR with the VR headset you wish to use.
|
||||
|
||||
2. Turn on **ONLY** the tracker which is attached on the VR headset.
|
||||
|
||||
3. Select the VR headset and tracker and calibrate.
|
||||
|
||||
4. Turn on your other devices.
|
||||
|
||||
5. You should see them line up with you as you after moving around your playspace for a bit for an initial calibration.
|
||||
|
||||
### Calibration outside VR
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define SPACECAL_VERSION_STRING "1.4"
|
||||
#define SPACECAL_VERSION_STRING "1.4-contcal-r8"
|
||||
@@ -1,81 +0,0 @@
|
||||
MinHook - The Minimalistic API Hooking Library for x64/x86
|
||||
Copyright (C) 2009-2017 Tsuda Kageyu.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY 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 THE COPYRIGHT HOLDER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================================
|
||||
Portions of this software are Copyright (c) 2008-2009, Vyacheslav Patkov.
|
||||
================================================================================
|
||||
Hacker Disassembler Engine 32 C
|
||||
Copyright (c) 2008-2009, Vyacheslav Patkov.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY 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 THE REGENTS OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Hacker Disassembler Engine 64 C
|
||||
Copyright (c) 2008-2009, Vyacheslav Patkov.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY 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 THE REGENTS OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* MinHook - The Minimalistic API Hooking Library for x64/x86
|
||||
* Copyright (C) 2009-2017 Tsuda Kageyu.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY 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 THE COPYRIGHT HOLDER
|
||||
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
|
||||
#error MinHook supports only x86 and x64 systems.
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// MinHook Error Codes.
|
||||
typedef enum MH_STATUS
|
||||
{
|
||||
// Unknown error. Should not be returned.
|
||||
MH_UNKNOWN = -1,
|
||||
|
||||
// Successful.
|
||||
MH_OK = 0,
|
||||
|
||||
// MinHook is already initialized.
|
||||
MH_ERROR_ALREADY_INITIALIZED,
|
||||
|
||||
// MinHook is not initialized yet, or already uninitialized.
|
||||
MH_ERROR_NOT_INITIALIZED,
|
||||
|
||||
// The hook for the specified target function is already created.
|
||||
MH_ERROR_ALREADY_CREATED,
|
||||
|
||||
// The hook for the specified target function is not created yet.
|
||||
MH_ERROR_NOT_CREATED,
|
||||
|
||||
// The hook for the specified target function is already enabled.
|
||||
MH_ERROR_ENABLED,
|
||||
|
||||
// The hook for the specified target function is not enabled yet, or already
|
||||
// disabled.
|
||||
MH_ERROR_DISABLED,
|
||||
|
||||
// The specified pointer is invalid. It points the address of non-allocated
|
||||
// and/or non-executable region.
|
||||
MH_ERROR_NOT_EXECUTABLE,
|
||||
|
||||
// The specified target function cannot be hooked.
|
||||
MH_ERROR_UNSUPPORTED_FUNCTION,
|
||||
|
||||
// Failed to allocate memory.
|
||||
MH_ERROR_MEMORY_ALLOC,
|
||||
|
||||
// Failed to change the memory protection.
|
||||
MH_ERROR_MEMORY_PROTECT,
|
||||
|
||||
// The specified module is not loaded.
|
||||
MH_ERROR_MODULE_NOT_FOUND,
|
||||
|
||||
// The specified function is not found.
|
||||
MH_ERROR_FUNCTION_NOT_FOUND
|
||||
}
|
||||
MH_STATUS;
|
||||
|
||||
// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
|
||||
// MH_QueueEnableHook or MH_QueueDisableHook.
|
||||
#define MH_ALL_HOOKS NULL
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Initialize the MinHook library. You must call this function EXACTLY ONCE
|
||||
// at the beginning of your program.
|
||||
MH_STATUS WINAPI MH_Initialize(VOID);
|
||||
|
||||
// Uninitialize the MinHook library. You must call this function EXACTLY
|
||||
// ONCE at the end of your program.
|
||||
MH_STATUS WINAPI MH_Uninitialize(VOID);
|
||||
|
||||
// Creates a hook for the specified target function, in disabled state.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function, which will be
|
||||
// overridden by the detour function.
|
||||
// pDetour [in] A pointer to the detour function, which will override
|
||||
// the target function.
|
||||
// ppOriginal [out] A pointer to the trampoline function, which will be
|
||||
// used to call the original target function.
|
||||
// This parameter can be NULL.
|
||||
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal);
|
||||
|
||||
// Creates a hook for the specified API function, in disabled state.
|
||||
// Parameters:
|
||||
// pszModule [in] A pointer to the loaded module name which contains the
|
||||
// target function.
|
||||
// pszProcName [in] A pointer to the target function name, which will be
|
||||
// overridden by the detour function.
|
||||
// pDetour [in] A pointer to the detour function, which will override
|
||||
// the target function.
|
||||
// ppOriginal [out] A pointer to the trampoline function, which will be
|
||||
// used to call the original target function.
|
||||
// This parameter can be NULL.
|
||||
MH_STATUS WINAPI MH_CreateHookApi(
|
||||
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal);
|
||||
|
||||
// Creates a hook for the specified API function, in disabled state.
|
||||
// Parameters:
|
||||
// pszModule [in] A pointer to the loaded module name which contains the
|
||||
// target function.
|
||||
// pszProcName [in] A pointer to the target function name, which will be
|
||||
// overridden by the detour function.
|
||||
// pDetour [in] A pointer to the detour function, which will override
|
||||
// the target function.
|
||||
// ppOriginal [out] A pointer to the trampoline function, which will be
|
||||
// used to call the original target function.
|
||||
// This parameter can be NULL.
|
||||
// ppTarget [out] A pointer to the target function, which will be used
|
||||
// with other functions.
|
||||
// This parameter can be NULL.
|
||||
MH_STATUS WINAPI MH_CreateHookApiEx(
|
||||
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget);
|
||||
|
||||
// Removes an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
|
||||
|
||||
// Enables an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// enabled in one go.
|
||||
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
|
||||
|
||||
// Disables an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// disabled in one go.
|
||||
MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
|
||||
|
||||
// Queues to enable an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// queued to be enabled.
|
||||
MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
|
||||
|
||||
// Queues to disable an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// queued to be disabled.
|
||||
MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
|
||||
|
||||
// Applies all queued changes in one go.
|
||||
MH_STATUS WINAPI MH_ApplyQueued(VOID);
|
||||
|
||||
// Translates the MH_STATUS to its name as a string.
|
||||
const char * WINAPI MH_StatusToString(MH_STATUS status);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Evan Pezent
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,166 @@
|
||||
# ImPlot
|
||||
ImPlot is an immediate mode, GPU accelerated plotting library for [Dear ImGui](https://github.com/ocornut/imgui). It aims to provide a first-class API that ImGui fans will love. ImPlot is well suited for visualizing program data in real-time or creating interactive plots, and requires minimal code to integrate. Just like ImGui, it does not burden the end user with GUI state management, avoids STL containers and C++ headers, and has no external dependencies except for ImGui itself.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/controls.gif" width="270"> <img src="https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/dnd.gif" width="270"> <img src="https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/pie.gif" width="270">
|
||||
|
||||
<img src="https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/query.gif" width="270"> <img src="https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/bars.gif" width="270">
|
||||
<img src="https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/rt.gif" width="270">
|
||||
|
||||
<img src="https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/stem.gif" width="270"> <img src="https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/markers.gif" width="270">
|
||||
<img src="https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/shaded.gif" width="270">
|
||||
|
||||
<img src="https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/candle.gif" width="270"> <img src="https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/heat.gif" width="270">
|
||||
<img src="https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/tables.gif" width="270">
|
||||
|
||||
## Features
|
||||
|
||||
- GPU accelerated rendering
|
||||
- multiple plot types:
|
||||
- line plots
|
||||
- shaded plots
|
||||
- scatter plots
|
||||
- vertical/horizontal/stacked bars graphs
|
||||
- vertical/horizontal error bars
|
||||
- stem plots
|
||||
- stair plots
|
||||
- pie charts
|
||||
- heatmap charts
|
||||
- 1D/2D histograms
|
||||
- images
|
||||
- and more likely to come
|
||||
- mix/match multiple plot items on a single plot
|
||||
- configurable axes ranges and scaling (linear/log)
|
||||
- subplots
|
||||
- time formatted x-axes (US formatted or ISO 8601)
|
||||
- reversible and lockable axes
|
||||
- multiple x-axes and y-axes
|
||||
- controls for zooming, panning, box selection, and auto-fitting data
|
||||
- controls for creating persistent query ranges (see demo)
|
||||
- several plot styling options: 10 marker types, adjustable marker sizes, line weights, outline colors, fill colors, etc.
|
||||
- 16 built-in colormaps and support for and user-added colormaps
|
||||
- optional plot titles, axis labels, and grid labels
|
||||
- optional and configurable legends with toggle buttons to quickly show/hide plot items
|
||||
- default styling based on current ImGui theme, or completely custom plot styles
|
||||
- customizable data getters and data striding (just like ImGui:PlotLine)
|
||||
- accepts data as float, double, and 8, 16, 32, and 64-bit signed/unsigned integral types
|
||||
- and more! (see Announcements [2020](https://github.com/epezent/implot/issues/48)/[2021](https://github.com/epezent/implot/issues/168))
|
||||
|
||||
## Usage
|
||||
|
||||
The API is used just like any other ImGui `BeginX`/`EndX` pair. First, start a new plot with `ImPlot::BeginPlot()`. Next, plot as many items as you want with the provided `PlotX` functions (e.g. `PlotLine()`, `PlotBars()`, `PlotScatter()`, etc). Finally, wrap things up with a call to `ImPlot::EndPlot()`. That's it!
|
||||
|
||||
```cpp
|
||||
int bar_data[11] = ...;
|
||||
float x_data[1000] = ...;
|
||||
float y_data[1000] = ...;
|
||||
|
||||
ImGui::Begin("My Window");
|
||||
if (ImPlot::BeginPlot("My Plot")) {
|
||||
ImPlot::PlotBars("My Bar Plot", bar_data, 11);
|
||||
ImPlot::PlotLine("My Line Plot", x_data, y_data, 1000);
|
||||
...
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
ImGui::End();
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
Of course, there's much more you can do with ImPlot...
|
||||
|
||||
## Demos
|
||||
|
||||
A comprehensive example of ImPlot's features can be found in `implot_demo.h`. Add this file to your sources and call `ImPlot::ShowDemoWindow()` somewhere in your update loop. You are encouraged to use this file as a reference when needing to implement various plot types. The demo is always updated to show new plot types and features as they are added, so check back with each release!
|
||||
|
||||
An online version of the demo is hosted [here](https://traineq.org/implot_demo/src/implot_demo.html). You can view the plots and the source code that generated them. Note that this demo may not always be up to date and is not as performant as a desktop implementation, but it should give you a general taste of what's possible with ImPlot. Special thanks to [pthom](https://github.com/pthom) for creating and hosting this!
|
||||
|
||||
More sophisticated demos requiring lengthier code and/or third-party libraries can be found in a separate repository: [implot_demos](https://github.com/epezent/implot_demos). Here, you will find advanced signal processing and ImPlot usage in action. Please read the `Contributing` section of that repository if you have an idea for a new demo!
|
||||
|
||||
## Integration
|
||||
|
||||
0) Set up an [ImGui](https://github.com/ocornut/imgui) environment if you don't already have one.
|
||||
1) Add `implot.h`, `implot_internal.h`, `implot.cpp`, `implot_items.cpp` and optionally `implot_demo.cpp` to your sources. Alternatively, you can get ImPlot using [vcpkg](https://github.com/microsoft/vcpkg/tree/master/ports/implot).
|
||||
2) Create and destroy an `ImPlotContext` wherever you do so for your `ImGuiContext`:
|
||||
|
||||
```cpp
|
||||
ImGui::CreateContext();
|
||||
ImPlot::CreateContext();
|
||||
...
|
||||
ImPlot::DestroyContext();
|
||||
ImGui::DestroyContext();
|
||||
```
|
||||
|
||||
You should be good to go!
|
||||
|
||||
If you want to test ImPlot quickly, consider trying [mahi-gui](https://github.com/mahilab/mahi-gui), which bundles ImGui, ImPlot, and several other packages for you.
|
||||
|
||||
## Extremely Important Note
|
||||
|
||||
Dear ImGui uses **16-bit indexing by default**, so high-density ImPlot widgets like `ImPlot::PlotHeatmap()` may produce too many vertices into `ImDrawList`, which causes an assertion failure and will result in data truncation and/or visual glitches. Therefore, it is **HIGHLY** recommended that you EITHER:
|
||||
|
||||
- **Option 1:** Enable 32-bit indices by uncommenting `#define ImDrawIdx unsigned int` in your ImGui [`imconfig.h`](https://github.com/ocornut/imgui/blob/master/imconfig.h#L89) file.
|
||||
- **Option 2:** Handle the `ImGuiBackendFlags_RendererHasVtxOffset` flag in your renderer if you must use 16-bit indices. Many of the default ImGui rendering backends already support `ImGuiBackendFlags_RendererHasVtxOffset`. Refer to [this issue](https://github.com/ocornut/imgui/issues/2591) for more information.
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Why?**
|
||||
|
||||
A: ImGui is an incredibly powerful tool for rapid prototyping and development, but provides only limited mechanisms for data visualization. Two dimensional plots are ubiquitous and useful to almost any application. Being able to visualize your data in real-time will give you insight and better understanding of your application.
|
||||
|
||||
**Q: Is ImPlot the right plotting library for me?**
|
||||
|
||||
A: If you're looking to generate publication quality plots and/or export plots to a file, ImPlot is NOT the library for you! ImPlot is geared toward plotting application data at realtime speeds with high levels of interactivity. ImPlot does its best to create pretty plots (indeed, there are quite a few styling options available), but it will always favor function over form.
|
||||
|
||||
**Q: Where is the documentation?**
|
||||
|
||||
A: The API is thoroughly commented in `implot.h`, and the demo in `implot_demo.cpp` should be more than enough to get you started. Also take a look at the [implot_demos](https://github.com/epezent/implot_demos) repository.
|
||||
|
||||
**Q: Is ImPlot suitable for plotting large datasets?**
|
||||
|
||||
A: Yes, within reason. You can plot tens to hundreds of thousands of points without issue, but don't expect millions to be a buttery smooth experience. That said, you can always downsample extremely large datasets by telling ImPlot to stride your data at larger intervals if needed. Also try the experimental `backends` branch which aims to provide GPU acceleration support.
|
||||
|
||||
**Q: What data types can I plot?**
|
||||
|
||||
A: ImPlot plotting functions accept most scalar types:
|
||||
`float`, `double`, `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int64`, `uint64`. Arrays of custom structs or classes (e.g. `Vector2f` or similar) are easily passed to ImPlot functions using the built in striding features (see `implot.h` for documentation), and many plotters provide a "getter" overload which accepts data generating callbacks.
|
||||
|
||||
**Q: Can plot styles be modified?**
|
||||
|
||||
A: Yes. Data colormaps and various styling colors and variables can be pushed/popped or modified permanently on startup. Three default styles are available, as well as an automatic style that attempts to match you ImGui style.
|
||||
|
||||
**Q: Does ImPlot support logarithmic scaling or time formatting?**
|
||||
|
||||
A: Yep! Both logscale and timescale are supported.
|
||||
|
||||
**Q: Does ImPlot support multiple y-axes? x-axes?**
|
||||
|
||||
A: Yes. Up to three x-axes and three y-axes can be enabled.
|
||||
|
||||
**Q: Does ImPlot support [insert plot type]?**
|
||||
|
||||
A: Maybe. Check the demo, gallery, or Announcements ([2020](https://github.com/epezent/implot/issues/48)/[2021](https://github.com/epezent/implot/issues/168))to see if your desired plot type is shown. If not, consider submitting an issue or better yet, a PR!
|
||||
|
||||
**Q: Does ImPlot support 3D plots?**
|
||||
|
||||
A: No, and likely never will since ImGui only deals in 2D rendering.
|
||||
|
||||
**Q: My plot lines look like crap!**
|
||||
|
||||
A: By default, no anti-aliasing is done on line plots for performance gains. If you use at least 4x MSAA, then you likely won't even notice. However, you can enable software AA per-plot with the `ImPlotFlags_AntiAliased` flag, or globally with `ImPlot::GetStyle().AntiAliasedLines = true;`.
|
||||
|
||||
**Q: Does ImPlot provide analytic tools?**
|
||||
|
||||
A: Not exactly, but it does give you the ability to query plot sub-ranges, with which you can process your data however you like.
|
||||
|
||||
**Q: Can plots be exported/saved to image?**
|
||||
|
||||
A: Not currently. Use your OS's screen capturing mechanisms if you need to capture a plot. ImPlot is not suitable for rendering publication quality plots; it is only intended to be used as a visualization tool. Post-process your data with MATLAB or matplotlib for these purposes.
|
||||
|
||||
**Q: Can I compile ImPlot as a dynamic library?**
|
||||
|
||||
A: Like ImGui, it is recommended that you compile and link ImPlot as a *static* library or directly as a part of your sources. However, if you must and are compiling ImPlot and ImGui as separate DLLs, make sure you set the current *ImGui* context with `ImPlot::SetImGuiContext(ImGuiContext* ctx)`. This ensures that global ImGui variables are correctly shared across the DLL boundary.
|
||||
|
||||
**Q: Can ImPlot be used with other languages/bindings?**
|
||||
|
||||
A: Yes, you can use the generated C binding, [cimplot](https://github.com/cimgui/cimplot) with most high level languages. [DearPyGui](https://github.com/hoffstadt/DearPyGui) provides a Python wrapper, among other things. [imgui-java](https://github.com/SpaiR/imgui-java) provides bindings for Java. A Rust binding, [implot-rs](https://github.com/4bb4/implot-rs), is currently in the works. An example using Emscripten can be found [here](https://github.com/pthom/implot_demo).
|
||||
@@ -0,0 +1,69 @@
|
||||
The list below represents a combination of high-priority work, nice-to-have features, and random ideas. We make no guarantees that all of this work will be completed or even started. If you see something that you need or would like to have, let us know, or better yet consider submitting a PR for the feature.
|
||||
|
||||
## API
|
||||
|
||||
- add shortcut/legacy overloads for BeginPlot
|
||||
|
||||
## Axes
|
||||
|
||||
- add flag to remove weekends on Time axis
|
||||
- pixel space scale (`ImPlotTransform_Display`), normalized space scale (`ImPlotTransform_Axes`), data space scale (`ImPloTransform_Data`)
|
||||
- make ImPlotFlags_Equal not a flag -> `SetupEqual(ImPlotAxis x, ImPlotAxis y)`
|
||||
- allow inverted arguments `SetAxes` to transpose data?
|
||||
- `SetupAxisColors()`
|
||||
- `SetupAxisConstraints()`
|
||||
- `SetupAxisHome()`
|
||||
|
||||
## Plot Items
|
||||
|
||||
- add `ImPlotLineFlags`, `ImPlotBarsFlags`, etc. for each plot type
|
||||
- add `PlotBarGroups` wrapper that makes rendering groups of bars easier, with stacked bar support
|
||||
- add `PlotBubbles` (see MATLAB bubble chart)
|
||||
- add non-zero references for `PlotBars` etc.
|
||||
- add exploding to `PlotPieChart` (on hover-highlight?)
|
||||
|
||||
## Styling
|
||||
|
||||
- support gradient and/or colormap sampled fills (e.g. ImPlotFillStyle_)
|
||||
- add hover/active color for plot axes
|
||||
- API for setting different fonts for plot elements
|
||||
|
||||
## Colormaps
|
||||
|
||||
- gradient editing tool
|
||||
- `RemoveColormap`
|
||||
|
||||
## Legend
|
||||
|
||||
- `ImPlotLegendFlags_SortItems`
|
||||
- `ImPlotLegendFlags_Scroll`
|
||||
- improve legend icons (e.g. adopt markers, gradients, etc)
|
||||
- make legend frame use ButtonBehavior (maybe impossible)
|
||||
|
||||
## Tools / Misc.
|
||||
|
||||
- add `IsPlotChanging` to detect change in limits
|
||||
- add ability to extend plot/axis context menus
|
||||
- add LTTB downsampling for lines
|
||||
- add box selection to axes
|
||||
- first frame render delay might fix "fit pop" effect
|
||||
- move some code to new `implot_tools.cpp`
|
||||
|
||||
## Optimizations
|
||||
|
||||
- find faster way to buffer data into ImDrawList (very slow)
|
||||
- reduce number of calls to `PushClipRect`
|
||||
- explore SIMD operations for high density plot items
|
||||
|
||||
## Completed
|
||||
- make BeginPlot take fewer args:
|
||||
- make query a tool -> `DragRect`
|
||||
- rework DragLine/Point to use ButtonBehavior
|
||||
- add support for multiple x-axes and don't limit count to 3
|
||||
- make axis side configurable (top/left, right/bottom) via new flag `ImPlotAxisFlags_Opposite`
|
||||
- add support for setting tick label strings via callback
|
||||
- give each axis an ID, remove ad-hoc DND solution
|
||||
- allow axis to be drag to opposite side (ala ImGui Table headers)
|
||||
- legend items can be hovered even if plot is not
|
||||
- fix frame delay on DragX tools
|
||||
- remove tag from drag line/point -> add `Tag` tool
|
||||
File diff suppressed because it is too large.
Load diff
+1143
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Submodule
+1
Submodule lib/minhook added at e33ec5abc0.
Reference in new issue
Block a user