Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc4ffa774d | ||
|
|
9a0204466a | ||
|
|
71e9683dc4 | ||
|
|
f21de937f0 | ||
|
|
aecc9612e2 | ||
|
|
0e8ab54400 | ||
|
|
c5c63da491 | ||
|
|
f779d68dff | ||
|
|
20a1054111 | ||
|
|
3e3d7750f1 | ||
|
|
37a951a4cc | ||
|
|
492743e4d0 | ||
|
|
d9af36f290 | ||
|
|
98760540e6 | ||
|
|
be51eeeda1 | ||
|
|
1e8191bcf1 | ||
|
|
889cdf9828 | ||
|
|
650d86dcc3 | ||
|
|
996373e07d | ||
|
|
50ecc1af1c | ||
|
|
50991f17a8 | ||
|
|
52c166e643 | ||
|
|
9b9e2119d4 | ||
|
|
af409fbed3 | ||
|
|
51372d9f0b | ||
|
|
a732606507 | ||
|
|
ea13a5b9f7 | ||
|
|
a3dabf06bf | ||
|
|
faa0462d72 | ||
|
|
e052c85dd5 | ||
|
|
1ca0537e83 | ||
|
|
040b98a513 | ||
|
|
c4379bb84d | ||
|
|
843b01486a | ||
|
|
8969600007 | ||
|
|
bebeb0c1cd | ||
|
|
3d546f2fd2 | ||
|
|
3f170b9ff1 | ||
|
|
6969cb8c5a | ||
|
|
6f1c83dd9f | ||
|
|
fcb6f13c5f | ||
|
|
81c55df127 | ||
|
|
1cc0583a5e | ||
|
|
15a16eb14c | ||
|
|
4dba5fd7fe | ||
|
|
984b93ffff | ||
|
|
55242e7d56 | ||
|
|
c6eb49d580 | ||
|
|
e599ac136d | ||
|
|
bd50a8c207 | ||
|
|
f3b4f9475e | ||
|
|
d060b98581 | ||
|
|
da869868a1 | ||
|
|
8e6796a9b5 | ||
|
|
6389d745ae | ||
|
|
326b54ba74 | ||
|
|
98b23ad5bb | ||
|
|
7788c2af38 | ||
|
|
5334084508 | ||
|
|
9752a3be05 | ||
|
|
8323d64b2d | ||
|
|
b04615dcb6 | ||
|
|
9d1fa11f98 | ||
|
|
ab5e80a8ef | ||
|
|
ffb301b38c | ||
|
|
9744585cf0 | ||
|
|
7cf363e315 | ||
|
|
9c4a906ec0 | ||
|
|
d6030195f9 | ||
|
|
c607fa30c4 | ||
|
|
eebf2877f3 | ||
|
|
7f60358eac | ||
|
|
85c6b3cea4 | ||
|
|
dda68d48b4 | ||
|
|
5c36f6e909 | ||
|
|
f112ca6768 | ||
|
|
a93dac0e28 | ||
|
|
35690d8521 | ||
|
|
1bed4afb97 | ||
|
|
6126280c40 | ||
|
|
f8abea143e | ||
|
|
ac16d42d01 | ||
|
|
5c500af0c6 | ||
|
|
c908dff357 | ||
|
|
7217b62e29 | ||
|
|
958136a12c | ||
|
|
92c0e1a0af | ||
|
|
963cd26e90 | ||
|
|
91e0cd15e1 | ||
|
|
e9f9c63768 | ||
|
|
7d4fc8f3d8 | ||
|
|
43ec1657c7 | ||
|
|
77a0375faf | ||
|
|
c240db83b1 |
No files matched your search
@@ -1,216 +0,0 @@
|
||||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: WebKit
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: DontAlign
|
||||
AlignTrailingComments:
|
||||
Kind: Never
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakAfterAttributes: Leave
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: All
|
||||
BreakBeforeBraces: WebKit
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 0
|
||||
CommentPragmas: "^ IWYU pragma:"
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: false
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: false
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: ^"(llvm|llvm-c|clang|clang-c)/
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: ^(<|"(gtest|gmock|isl|json)/)
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: .*
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: (Test)?$
|
||||
IncludeIsMainSourceRegex: ""
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
InsertNewlineAtEOF: false
|
||||
InsertTrailingCommas: None
|
||||
IntegerLiteralSeparator:
|
||||
Binary: 0
|
||||
BinaryMinDigits: 0
|
||||
Decimal: 0
|
||||
DecimalMinDigits: 0
|
||||
Hex: 0
|
||||
HexMinDigits: 0
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
LambdaBodyIndentation: Signature
|
||||
LineEnding: CRLF
|
||||
MacroBlockBegin: ""
|
||||
MacroBlockEnd: ""
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: Inner
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PPIndentWidth: -1
|
||||
PackConstructorInitializers: BinPack
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Left
|
||||
QualifierAlignment: Leave
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: true
|
||||
RemoveBracesLLVM: false
|
||||
RemoveSemicolon: false
|
||||
RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SortIncludes: CaseInsensitive
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: LexicographicNumeric
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: true
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterForeachMacros: true
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: true
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Latest
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
WhitespaceSensitiveMacros:
|
||||
- BOOST_PP_STRINGIZE
|
||||
- CF_SWIFT_NAME
|
||||
- NS_SWIFT_NAME
|
||||
- PP_STRINGIZE
|
||||
- STRINGIZE
|
||||
@@ -1,10 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = crlf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.c,*.cc,*.cpp,*.hpp,*.h]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
@@ -1,7 +0,0 @@
|
||||
* text=auto eol=crlf
|
||||
*.cpp text eol=crlf
|
||||
*.cc text eol=crlf
|
||||
*.c text eol=crlf
|
||||
*.h text eol=crlf
|
||||
*.hpp text eol=crlf
|
||||
*.sh text eol=lf
|
||||
@@ -1,2 +0,0 @@
|
||||
ko_fi: hyblocker
|
||||
patreon: hekky
|
||||
@@ -1,53 +1,9 @@
|
||||
---
|
||||
name: Bug report
|
||||
description: Report an issue with Space Calibrator.
|
||||
title: '[Bug]: '
|
||||
labels:
|
||||
- bug
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and short description of the problem
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: >-
|
||||
Provide clear steps to trigger the issue if possible. If you're unsure,
|
||||
describe what you did prior to it happening and what you tried doing to
|
||||
solve it.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behaviour
|
||||
description: Describe what you expected to happen
|
||||
- type: input
|
||||
attributes:
|
||||
label: VR headset
|
||||
description: >-
|
||||
The VR headset you are using, e.g. Meta Quest 3, Pico 4, Steam Frame,
|
||||
Samsung Galaxy XR, etc.
|
||||
placeholder: Meta Quest 3
|
||||
- type: input
|
||||
attributes:
|
||||
label: Connection method
|
||||
description: >-
|
||||
How you are connecting your VR headset to your computer, eg Virtual
|
||||
Desktop, Steam Link, Air Link, Oculus Link Cable.
|
||||
placeholder: Steam Link
|
||||
- type: input
|
||||
attributes:
|
||||
label: What are you calibrating with?
|
||||
description: List the trackers you are using here if you're unsure.
|
||||
placeholder: Vive Tracker 3.0
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >-
|
||||
You are advised to also provide logs to help make it easier for me to
|
||||
troubleshoot your issues. You can go to Space Calibrator -> Settings and
|
||||
click the [View logs] button to open the folder. Please attach
|
||||
`log_overlay_latest.log` and `log_driver_latest.log`. Thank you!
|
||||
about: Something is broken
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 56 KiB |
@@ -1,53 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
branches: [ "develop" ]
|
||||
pull_request:
|
||||
branches: [ "develop" ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
BUILD_TYPE: RelWithDebInfo
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build (${{ matrix.name }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: windows-x64
|
||||
os: windows-2025-vs2026
|
||||
artifact-name: space-calibrator-win64
|
||||
|
||||
- name: linux-x64
|
||||
os: ubuntu-24.04
|
||||
container: registry.gitlab.steamos.cloud/steamrt/steamrt4/sdk:latest
|
||||
artifact-name: space-calibrator-linux-x64
|
||||
|
||||
- name: linux-arm64
|
||||
os: ubuntu-24.04-arm
|
||||
container: registry.gitlab.steamos.cloud/steamrt/steamrt4/sdk/arm64:latest
|
||||
artifact-name: space-calibrator-linux-arm64
|
||||
|
||||
container: ${{ matrix.container }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ./build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ./build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: |
|
||||
./build/artifacts/
|
||||
./build/driver_01spacecalibrator/
|
||||
@@ -4,9 +4,9 @@ ipch
|
||||
*.opendb
|
||||
*.aps
|
||||
*.user
|
||||
.code
|
||||
/install/*.exe
|
||||
[bB]in
|
||||
[bB]in_linux64
|
||||
[bB]in_linuxaarch64
|
||||
[oO]ut
|
||||
/OpenVR-SpaceCalibrator/x64/
|
||||
/OpenVR-SpaceCalibratorDriver/x64/
|
||||
/x64/
|
||||
/lib/boost*
|
||||
/install/OpenVR-SpaceCalibrator.exe
|
||||
lib/Release/
|
||||
@@ -1,39 +1,3 @@
|
||||
[submodule "vendor/imgui"]
|
||||
path = vendor/imgui
|
||||
url = https://github.com/ocornut/imgui
|
||||
[submodule "vendor/openvr"]
|
||||
path = vendor/openvr
|
||||
url = https://github.com/ValveSoftware/openvr
|
||||
[submodule "vendor/glfw"]
|
||||
path = vendor/glfw
|
||||
url = https://github.com/glfw/glfw
|
||||
[submodule "vendor/minhook"]
|
||||
path = vendor/minhook
|
||||
url = https://github.com/TsudaKageyu/minhook
|
||||
[submodule "vendor/fmt"]
|
||||
path = vendor/fmt
|
||||
url = https://github.com/fmtlib/fmt
|
||||
[submodule "vendor/quill"]
|
||||
path = vendor/quill
|
||||
url = https://github.com/odygrd/quill
|
||||
[submodule "vendor/glaze"]
|
||||
path = vendor/glaze
|
||||
url = https://github.com/stephenberry/glaze.git
|
||||
[submodule "vendor/volk"]
|
||||
path = vendor/volk
|
||||
url = https://github.com/zeux/volk.git
|
||||
[submodule "vendor/Vulkan-Headers"]
|
||||
path = vendor/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "vendor/implot"]
|
||||
path = vendor/implot
|
||||
url = https://github.com/epezent/implot
|
||||
[submodule "vendor/simpleble"]
|
||||
path = vendor/simpleble
|
||||
url = https://github.com/simpleble/simpleble
|
||||
[submodule "vendor/implot3d"]
|
||||
path = vendor/implot3d
|
||||
url = git@github.com:brenocq/implot3d.git
|
||||
[submodule "vendor/zstd"]
|
||||
path = vendor/zstd
|
||||
url = https://github.com/facebook/zstd.git
|
||||
[submodule "lib/minhook"]
|
||||
path = lib/minhook
|
||||
url = https://github.com/bdunderscore/minhook.git
|
||||
@@ -1,91 +0,0 @@
|
||||
cmake_minimum_required (VERSION 3.8)
|
||||
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Build directory
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/artifacts)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/artifacts)
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/artifacts)
|
||||
|
||||
# Function because lazy
|
||||
function(adjust_bin_paths lib)
|
||||
set_target_properties(${lib}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/artifacts
|
||||
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/artifacts
|
||||
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/artifacts
|
||||
)
|
||||
endfunction()
|
||||
|
||||
# windows prefers win8.1 sdk instead of win 10 sdk for some god forsaken reason
|
||||
if(WIN32)
|
||||
set(CMAKE_SYSTEM_VERSION 10.0 CACHE STRING "Forcing Win10 SDK" FORCE)
|
||||
else()
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
project("SpaceCalibrator")
|
||||
|
||||
# 3rd party stuff
|
||||
add_subdirectory ("vendor")
|
||||
|
||||
# warnings for our code
|
||||
if ("${CMAKE_CXX_COMPILER}" MATCHES "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
|
||||
# add_compile_options(-Werror=return-type -Wall -Wextra -Wpedantic /MP)
|
||||
add_compile_options(-Werror=return-type)
|
||||
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||
# add_compile_options(-Werror=return-type -Wall -Wextra -Wpedantic)
|
||||
add_compile_options(-Werror=return-type -Wno-psabi)
|
||||
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Intel")
|
||||
# add_compile_options(-Werror=return-type -Wall -Wextra -Wpedantic)
|
||||
add_compile_options(/Qdiag-error-return)
|
||||
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC")
|
||||
add_compile_options(
|
||||
# treat warnings as errors
|
||||
/we4715 # not all control paths return a value
|
||||
/we4828 # disallow invalid characters
|
||||
# prinf-like functions: format mismatch
|
||||
/we4473 # : not enough arguments passed for format string
|
||||
/we4474 # : too many arguments passed for format string
|
||||
/we4475 # : length modifier cannot be used with type field character in format specifier
|
||||
/we4476 # : unknown type field character in format specifier
|
||||
/we4477 # : format string requires an argument of type , but variadic argument has type
|
||||
/we4478 # : positional and non-positional placeholders cannot be mixed in the same format string
|
||||
/we4775 # nonstandard extension used in format string of function
|
||||
/we4776 # % is not allowed in the format string of function
|
||||
/we4777 # : format string requires an argument of type , but variadic argument has type
|
||||
/we4778 # : unterminated format string
|
||||
# macro arg mismatch
|
||||
/we4002 # too many actual parameters for macro 'identifier'
|
||||
/we4003 # not enough actual parameters for macro 'identifier'
|
||||
/Zc:threadSafeInit- # https://connect.microsoft.com/VisualStudio/feedback/details/1789709/visual-c-2015-runtime-broken-on-windows-server-2003-c-11-magic-statics
|
||||
/MP # multiprocessor compilation
|
||||
/utf-8 # utf-8 source & exec
|
||||
/GF) # eliminate duplicate strings
|
||||
endif()
|
||||
|
||||
# Match SteamVR build flags on MSVC
|
||||
if(MSVC)
|
||||
add_compile_options(/guard:cf /GS)
|
||||
add_link_options("/GUARD:CF")
|
||||
add_compile_options($<$<CONFIG:Release>:/Ot> $<$<CONFIG:Release>:/Oi> $<$<CONFIG:Release>:/Gy>)
|
||||
endif()
|
||||
|
||||
# force relative RPATH for linux so that shared objects may be found relative to the binary (fixes Steam launch)
|
||||
if (UNIX AND NOT APPLE)
|
||||
SET(CMAKE_SKIP_BUILD_RPATH FALSE)
|
||||
SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
|
||||
SET(CMAKE_INSTALL_RPATH "")
|
||||
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
|
||||
SET(CMAKE_BUILD_RPATH_USE_ORIGIN TRUE)
|
||||
endif()
|
||||
|
||||
# add editorconfig to vcxprojs
|
||||
if (WIN32)
|
||||
add_custom_target(FormattingRules SOURCES "${CMAKE_SOURCE_DIR}/.editorconfig" "${CMAKE_SOURCE_DIR}/.clang-format")
|
||||
set_property(TARGET FormattingRules PROPERTY FOLDER "Solution Items")
|
||||
endif()
|
||||
|
||||
# Include project
|
||||
add_subdirectory ("src")
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "x64-Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": ""
|
||||
},
|
||||
{
|
||||
"name": "x64-Release",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "RelWithDebInfo",
|
||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": ""
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": ""
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-Release",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "RelWithDebInfo",
|
||||
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
@ECHO OFF
|
||||
cmake -G "Visual Studio 17 2022" -A x64 -B bin -S .
|
||||
@@ -1,8 +1,7 @@
|
||||
== Space Calibrator ==
|
||||
== OpenVR-SpaceCalibrator ==
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023-2026 Hyblocker and other contributors
|
||||
Copyright (c) 2020-2022 Justin Li and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
@@ -113,7 +112,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2026 Omar Cornut
|
||||
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
|
||||
@@ -137,8 +136,7 @@ SOFTWARE.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2024 Evan Pezent
|
||||
Copyright (c) 2025-2026 Breno Cunha Queiroz
|
||||
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
|
||||
@@ -161,8 +159,7 @@ SOFTWARE.
|
||||
== glfw ==
|
||||
|
||||
Copyright (c) 2002-2006 Marcus Geelnard
|
||||
|
||||
Copyright (c) 2006-2019 Camilla Löwy
|
||||
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
|
||||
@@ -183,71 +180,31 @@ freely, subject to the following restrictions:
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
|
||||
== glad ==
|
||||
|
||||
The glad source code:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2022 David Herberth
|
||||
|
||||
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.
|
||||
== gl3w ==
|
||||
|
||||
|
||||
The Khronos Specifications:
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Copyright (c) 2013-2020 The Khronos Group Inc.
|
||||
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.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
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.
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
The EGL Specification and various headers:
|
||||
|
||||
Copyright (c) 2007-2016 The Khronos Group Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and/or associated documentation files (the
|
||||
"Materials"), to deal in the Materials without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Materials, and to
|
||||
permit persons to whom the Materials are 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 Materials.
|
||||
|
||||
THE MATERIALS ARE 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
|
||||
MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
||||
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 ==
|
||||
|
||||
@@ -628,237 +585,4 @@ 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.
|
||||
|
||||
== SimpleBLE ==
|
||||
|
||||
Official builds include SimpleBLE under a commercial license granted to Hyblocker. That license doesn’t
|
||||
cover forks. SimpleBLE itself is under BSL 1.1 — use that (or get your own commercial license) if you
|
||||
build/fork from source.
|
||||
|
||||
# SimpleBLE Commercial License Agreement
|
||||
|
||||
|
||||
Please [email us](mailto:contact@simpleble.org) or [leave us a message on our website](https://www.simpleble.org/contact?utm_source=github&utm_medium=referral&utm_campaign=simpleble_license) if you would like to purchase a commercial license or would like more information.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# Business Source License 1.1
|
||||
|
||||
License text copyright © 2017 MariaDB Corporation Ab, All Rights Reserved. "Business Source License"
|
||||
is a trademark of MariaDB Corporation Ab.
|
||||
|
||||
## Terms
|
||||
|
||||
The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and
|
||||
make non-production use of the Licensed Work. The Licensor may make an Additional Use Grant, above,
|
||||
permitting limited production use.
|
||||
|
||||
Effective on the Change Date, or the fourth anniversary of the first publicly available distribution
|
||||
of a specific version of the Licensed Work under this License, whichever comes first, the Licensor
|
||||
hereby grants you rights under the terms of the Change License, and the rights granted in the
|
||||
paragraph above terminate.
|
||||
|
||||
If your use of the Licensed Work does not comply with the requirements currently in effect as
|
||||
described in this License, you must purchase a commercial license from the Licensor, its affiliated
|
||||
entities, or authorized resellers, or you must refrain from using the Licensed Work.
|
||||
|
||||
All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are
|
||||
subject to this License. This License applies separately for each version of the Licensed Work and
|
||||
the Change Date may vary for each version of the Licensed Work released by Licensor.
|
||||
|
||||
You must conspicuously display this License on each original or modified copy of the Licensed Work.
|
||||
If you receive the Licensed Work in original or modified form from a third party, the terms and
|
||||
conditions set forth in this License apply to your use of that work.
|
||||
|
||||
Any use of the Licensed Work in violation of this License will automatically terminate your rights
|
||||
under this License for the current and all other versions of the Licensed Work.
|
||||
|
||||
This License does not grant you any right in any trademark or logo of Licensor or its affiliates
|
||||
(provided that you may use a trademark or logo of Licensor as expressly required by this License).
|
||||
|
||||
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN "AS IS" BASIS. LICENSOR
|
||||
HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION)
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE.
|
||||
|
||||
== glaze ==
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - present, Stephen Berry
|
||||
|
||||
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.
|
||||
|
||||
== quill ==
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 - present, Odysseas Georgoudis
|
||||
|
||||
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.
|
||||
|
||||
== fmt ==
|
||||
|
||||
Copyright (c) 2012 - present, Victor Zverovich and {fmt} 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 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.
|
||||
|
||||
== volk ==
|
||||
|
||||
Copyright (c) 2018-2026 Arseny Kapoulkine
|
||||
|
||||
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.
|
||||
|
||||
== Vulkan-Headers ==
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015-2023 The Khronos Group Inc.
|
||||
|
||||
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.
|
||||
|
||||
== IconFontCppHeaders ==
|
||||
|
||||
Copyright (c) 2017 Juliette Foucaut and Doug Binks
|
||||
|
||||
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.
|
||||
|
||||
== implot3d ==
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-2026 Breno Cunha Queiroz
|
||||
|
||||
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.
|
||||
|
||||
== zstd ==
|
||||
|
||||
BSD License
|
||||
|
||||
For Zstandard software
|
||||
|
||||
Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook, nor Meta, nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL 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.
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
ARCH=$(uname -m)
|
||||
|
||||
if [ "$ARCH" = "x86_64" ]; then
|
||||
exec "$SCRIPT_DIR/SpaceCalibratorx64" "$@"
|
||||
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
|
||||
if [ -e "$SCRIPT_DIR/SpaceCalibratorArm64" ]; then
|
||||
exec "$SCRIPT_DIR/SpaceCalibratorArm64" "$@"
|
||||
else
|
||||
echo "SpaceCalibrator: aarch64 binary missing, falling back to x64..."
|
||||
exec "$SCRIPT_DIR/SpaceCalibratorx64" "$@"
|
||||
fi
|
||||
else
|
||||
echo "SpaceCalibrator: Unsupported architecture $ARCH"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1 +0,0 @@
|
||||
You may find the source code of this program at https://github.com/hyblocker/OpenVR-SpaceCalibrator . You should also have a LICENSE file alongside this one.
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# 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
|
||||
@@ -0,0 +1,556 @@
|
||||
#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>
|
||||
#include <iostream>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
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),
|
||||
(lhs.w * rhs.x) + (lhs.x * rhs.w) + (lhs.y * rhs.z) - (lhs.z * rhs.y),
|
||||
(lhs.w * rhs.y) + (lhs.y * rhs.w) + (lhs.z * rhs.x) - (lhs.x * rhs.z),
|
||||
(lhs.w * rhs.z) + (lhs.z * rhs.w) + (lhs.x * rhs.y) - (lhs.y * rhs.x)
|
||||
};
|
||||
}
|
||||
|
||||
CalibrationContext CalCtx;
|
||||
IPCClient Driver;
|
||||
static protocol::DriverPoseShmem shmem;
|
||||
|
||||
namespace {
|
||||
CalibrationCalc calibration;
|
||||
|
||||
inline vr::HmdVector3d_t quaternionRotateVector(const vr::HmdQuaternion_t& quat, const double(&vector)[3]) {
|
||||
vr::HmdQuaternion_t vectorQuat = { 0.0, vector[0], vector[1] , vector[2] };
|
||||
vr::HmdQuaternion_t conjugate = { quat.w, -quat.x, -quat.y, -quat.z };
|
||||
auto rotatedVectorQuat = quat * vectorQuat * conjugate;
|
||||
return { rotatedVectorQuat.x, rotatedVectorQuat.y, rotatedVectorQuat.z };
|
||||
}
|
||||
|
||||
inline Eigen::Matrix3d quaternionRotateMatrix(const vr::HmdQuaternion_t& quat) {
|
||||
return Eigen::Quaterniond(quat.w, quat.x, quat.y, quat.z).toRotationMatrix();
|
||||
}
|
||||
|
||||
struct DSample
|
||||
{
|
||||
bool valid;
|
||||
Eigen::Vector3d ref, target;
|
||||
};
|
||||
|
||||
bool StartsWith(const std::string& str, const std::string& prefix)
|
||||
{
|
||||
if (str.length() < prefix.length())
|
||||
return false;
|
||||
|
||||
return str.compare(0, prefix.length(), prefix) == 0;
|
||||
}
|
||||
|
||||
bool EndsWith(const std::string& str, const std::string& suffix)
|
||||
{
|
||||
if (str.length() < suffix.length())
|
||||
return false;
|
||||
|
||||
return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
|
||||
}
|
||||
|
||||
Eigen::Vector3d AxisFromRotationMatrix3(Eigen::Matrix3d rot)
|
||||
{
|
||||
return Eigen::Vector3d(rot(2, 1) - rot(1, 2), rot(0, 2) - rot(2, 0), rot(1, 0) - rot(0, 1));
|
||||
}
|
||||
|
||||
double AngleFromRotationMatrix3(Eigen::Matrix3d rot)
|
||||
{
|
||||
return acos((rot(0, 0) + rot(1, 1) + rot(2, 2) - 1.0) / 2.0);
|
||||
}
|
||||
|
||||
vr::HmdQuaternion_t VRRotationQuat(const Eigen::Quaterniond& rotQuat)
|
||||
{
|
||||
|
||||
vr::HmdQuaternion_t vrRotQuat;
|
||||
vrRotQuat.x = rotQuat.coeffs()[0];
|
||||
vrRotQuat.y = rotQuat.coeffs()[1];
|
||||
vrRotQuat.z = rotQuat.coeffs()[2];
|
||||
vrRotQuat.w = rotQuat.coeffs()[3];
|
||||
return vrRotQuat;
|
||||
}
|
||||
|
||||
vr::HmdQuaternion_t VRRotationQuat(Eigen::Vector3d eulerdeg)
|
||||
{
|
||||
auto euler = eulerdeg * EIGEN_PI / 180.0;
|
||||
|
||||
Eigen::Quaterniond rotQuat =
|
||||
Eigen::AngleAxisd(euler(0), Eigen::Vector3d::UnitZ()) *
|
||||
Eigen::AngleAxisd(euler(1), Eigen::Vector3d::UnitY()) *
|
||||
Eigen::AngleAxisd(euler(2), Eigen::Vector3d::UnitX());
|
||||
|
||||
return VRRotationQuat(rotQuat);
|
||||
}
|
||||
|
||||
vr::HmdVector3d_t VRTranslationVec(Eigen::Vector3d transcm)
|
||||
{
|
||||
auto trans = transcm * 0.01;
|
||||
vr::HmdVector3d_t vrTrans;
|
||||
vrTrans.v[0] = trans[0];
|
||||
vrTrans.v[1] = trans[1];
|
||||
vrTrans.v[2] = trans[2];
|
||||
return vrTrans;
|
||||
}
|
||||
|
||||
DSample DeltaRotationSamples(Sample s1, Sample s2)
|
||||
{
|
||||
// Difference in rotation between samples.
|
||||
auto dref = s1.ref.rot * s2.ref.rot.transpose();
|
||||
auto dtarget = s1.target.rot * s2.target.rot.transpose();
|
||||
|
||||
// When stuck together, the two tracked objects rotate as a pair,
|
||||
// therefore their axes of rotation must be equal between any given pair of samples.
|
||||
DSample ds;
|
||||
ds.ref = AxisFromRotationMatrix3(dref);
|
||||
ds.target = AxisFromRotationMatrix3(dtarget);
|
||||
|
||||
// Reject samples that were too close to each other.
|
||||
auto refA = AngleFromRotationMatrix3(dref);
|
||||
auto targetA = AngleFromRotationMatrix3(dtarget);
|
||||
ds.valid = refA > 0.4 && targetA > 0.4 && ds.ref.norm() > 0.01 && ds.target.norm() > 0.01;
|
||||
|
||||
ds.ref.normalize();
|
||||
ds.target.normalize();
|
||||
return ds;
|
||||
}
|
||||
|
||||
Pose ConvertPose(const vr::DriverPose_t &driverPose) {
|
||||
Eigen::Quaterniond driverToWorldQ(
|
||||
driverPose.qWorldFromDriverRotation.w,
|
||||
driverPose.qWorldFromDriverRotation.x,
|
||||
driverPose.qWorldFromDriverRotation.y,
|
||||
driverPose.qWorldFromDriverRotation.z
|
||||
);
|
||||
Eigen::Vector3d driverToWorldV(
|
||||
driverPose.vecWorldFromDriverTranslation[0],
|
||||
driverPose.vecWorldFromDriverTranslation[1],
|
||||
driverPose.vecWorldFromDriverTranslation[2]
|
||||
);
|
||||
|
||||
Eigen::Quaterniond driverRot = driverToWorldQ * Eigen::Quaterniond(
|
||||
driverPose.qRotation.w,
|
||||
driverPose.qRotation.x,
|
||||
driverPose.qRotation.y,
|
||||
driverPose.qRotation.z
|
||||
);
|
||||
|
||||
Eigen::Vector3d driverPos = driverToWorldV + driverToWorldQ * Eigen::Vector3d(
|
||||
driverPose.vecPosition[0],
|
||||
driverPose.vecPosition[1],
|
||||
driverPose.vecPosition[2]
|
||||
);
|
||||
|
||||
Eigen::AffineCompact3d xform = Eigen::Translation3d(driverPos) * driverRot;
|
||||
|
||||
return Pose(xform);
|
||||
}
|
||||
|
||||
bool CollectSample(const CalibrationContext& ctx)
|
||||
{
|
||||
vr::DriverPose_t reference, target;
|
||||
reference.poseIsValid = false;
|
||||
target.poseIsValid = false;
|
||||
|
||||
reference = ctx.devicePoses[ctx.referenceID];
|
||||
target = ctx.devicePoses[ctx.targetID];
|
||||
|
||||
bool ok = true;
|
||||
if (!reference.poseIsValid)
|
||||
{
|
||||
CalCtx.Log("Reference device is not tracking\n"); ok = false;
|
||||
}
|
||||
if (!target.poseIsValid)
|
||||
{
|
||||
CalCtx.Log("Target device is not tracking\n"); ok = false;
|
||||
}
|
||||
if (!ok)
|
||||
{
|
||||
if (CalCtx.state != CalibrationState::Continuous) {
|
||||
CalCtx.Log("Aborting calibration!\n");
|
||||
CalCtx.state = CalibrationState::None;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
calibration.PushSample(Sample(
|
||||
ConvertPose(reference),
|
||||
ConvertPose(target)
|
||||
));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return CalCtx.referenceID >= 0 && CalCtx.targetID >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
void InitCalibrator()
|
||||
{
|
||||
Driver.Connect();
|
||||
shmem.Open(OPENVR_SPACECALIBRATOR_SHMEM_NAME);
|
||||
}
|
||||
|
||||
void ResetAndDisableOffsets(uint32_t id)
|
||||
{
|
||||
vr::HmdVector3d_t zeroV;
|
||||
zeroV.v[0] = zeroV.v[1] = zeroV.v[2] = 0;
|
||||
|
||||
vr::HmdQuaternion_t zeroQ;
|
||||
zeroQ.x = 0; zeroQ.y = 0; zeroQ.z = 0; zeroQ.w = 1;
|
||||
|
||||
protocol::Request req(protocol::RequestSetDeviceTransform);
|
||||
req.setDeviceTransform = { id, false, zeroV, zeroQ, 1.0 };
|
||||
Driver.SendBlocking(req);
|
||||
}
|
||||
|
||||
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]);
|
||||
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);
|
||||
if (deviceClass == vr::TrackedDeviceClass_Invalid)
|
||||
continue;
|
||||
|
||||
/*if (deviceClass == vr::TrackedDeviceClass_HMD) // for debugging unexpected universe switches
|
||||
{
|
||||
vr::ETrackedPropertyError err = vr::TrackedProp_Success;
|
||||
auto universeId = vr::VRSystem()->GetUint64TrackedDeviceProperty(id, vr::Prop_CurrentUniverseId_Uint64, &err);
|
||||
printf("uid %d err %d\n", universeId, err);
|
||||
ResetAndDisableOffsets(id);
|
||||
continue;
|
||||
}*/
|
||||
|
||||
if (!ctx.enabled)
|
||||
{
|
||||
ResetAndDisableOffsets(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
vr::ETrackedPropertyError err = vr::TrackedProp_Success;
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_TrackingSystemName_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
||||
|
||||
if (err != vr::TrackedProp_Success)
|
||||
{
|
||||
ResetAndDisableOffsets(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string trackingSystem(buffer);
|
||||
|
||||
if (id == vr::k_unTrackedDeviceIndex_Hmd)
|
||||
{
|
||||
//auto p = ctx.devicePoses[id].mDeviceToAbsoluteTracking.m;
|
||||
//printf("HMD %d: %f %f %f\n", id, p[0][3], p[1][3], p[2][3]);
|
||||
|
||||
if (trackingSystem != ctx.referenceTrackingSystem)
|
||||
{
|
||||
// Currently using an HMD with a different tracking system than the calibration.
|
||||
ctx.enabled = false;
|
||||
}
|
||||
|
||||
ResetAndDisableOffsets(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trackingSystem != ctx.targetTrackingSystem)
|
||||
{
|
||||
ResetAndDisableOffsets(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
protocol::Request req(protocol::RequestSetDeviceTransform);
|
||||
req.setDeviceTransform = {
|
||||
id,
|
||||
true,
|
||||
VRTranslationVec(ctx.calibratedTranslation),
|
||||
VRRotationQuat(ctx.calibratedRotation),
|
||||
ctx.calibratedScale
|
||||
};
|
||||
req.setDeviceTransform.lerp = CalCtx.state == CalibrationState::Continuous;
|
||||
req.setDeviceTransform.quash = CalCtx.state == CalibrationState::Continuous && id == CalCtx.targetID && CalCtx.quashTargetInContinuous;
|
||||
|
||||
Driver.SendBlocking(req);
|
||||
}
|
||||
|
||||
if (ctx.enabled && ctx.chaperone.valid && ctx.chaperone.autoApply)
|
||||
{
|
||||
uint32_t quadCount = 0;
|
||||
vr::VRChaperoneSetup()->GetLiveCollisionBoundsInfo(nullptr, &quadCount);
|
||||
|
||||
// Heuristic: when SteamVR resets to a blank-ish chaperone, it uses empty geometry,
|
||||
// but manual adjustments (e.g. via a play space mover) will not touch geometry.
|
||||
if (quadCount != ctx.chaperone.geometry.size())
|
||||
{
|
||||
ApplyChaperoneBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StartCalibration()
|
||||
{
|
||||
CalCtx.state = CalibrationState::Begin;
|
||||
CalCtx.wantedUpdateInterval = 0.0;
|
||||
CalCtx.messages.clear();
|
||||
calibration.Clear();
|
||||
Metrics::WriteLogAnnotation("StartCalibration");
|
||||
}
|
||||
|
||||
void StartContinuousCalibration() {
|
||||
CalCtx.calibrationSpeed = CalibrationContext::SLOW;
|
||||
StartCalibration();
|
||||
CalCtx.state = CalibrationState::Continuous;
|
||||
calibration.setRelativeTransformation(CalCtx.refToTargetPose, CalCtx.relativePosCalibrated);
|
||||
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)
|
||||
{
|
||||
if (!vr::VRSystem())
|
||||
return;
|
||||
|
||||
auto &ctx = CalCtx;
|
||||
if ((time - ctx.timeLastTick) < 0.05)
|
||||
return;
|
||||
|
||||
if (ctx.state == CalibrationState::Continuous || ctx.state == CalibrationState::ContinuousStandby) {
|
||||
ctx.ClearLogOnMessage();
|
||||
}
|
||||
|
||||
ctx.timeLastTick = time;
|
||||
shmem.ReadNewPoses([&](const protocol::DriverPoseShmem::AugmentedPose& augmented_pose) {
|
||||
if (augmented_pose.deviceId >= 0 && augmented_pose.deviceId <= vr::k_unMaxTrackedDeviceCount) {
|
||||
ctx.devicePoses[augmented_pose.deviceId] = augmented_pose.pose;
|
||||
}
|
||||
});
|
||||
|
||||
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 (AssignTargets()) {
|
||||
StartContinuousCalibration();
|
||||
}
|
||||
else {
|
||||
ctx.wantedUpdateInterval = 0.5;
|
||||
ctx.Log("Waiting for devices...");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.state == CalibrationState::None) {
|
||||
ctx.wantedUpdateInterval = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.state == CalibrationState::Editing)
|
||||
{
|
||||
ctx.wantedUpdateInterval = 0.1;
|
||||
|
||||
if ((time - ctx.timeLastScan) >= 0.1)
|
||||
{
|
||||
ScanAndApplyProfile(ctx);
|
||||
ctx.timeLastScan = time;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
|
||||
if (ctx.referenceID == -1 || ctx.referenceID >= vr::k_unMaxTrackedDeviceCount) {
|
||||
CalCtx.Log("Missing reference device\n");
|
||||
ok = false;
|
||||
}
|
||||
if (ctx.targetID == -1 || ctx.targetID >= vr::k_unMaxTrackedDeviceCount)
|
||||
{
|
||||
CalCtx.Log("Missing target device\n");
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (ctx.state == CalibrationState::Begin)
|
||||
{
|
||||
|
||||
char referenceSerial[256], targetSerial[256];
|
||||
referenceSerial[0] = targetSerial[0] = 0;
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(ctx.referenceID, vr::Prop_SerialNumber_String, referenceSerial, 256);
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(ctx.targetID, vr::Prop_SerialNumber_String, targetSerial, 256);
|
||||
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof buf, "Reference device ID: %d, serial: %s\n", ctx.referenceID, referenceSerial);
|
||||
CalCtx.Log(buf);
|
||||
snprintf(buf, sizeof buf, "Target device ID: %d, serial %s\n", ctx.targetID, targetSerial);
|
||||
CalCtx.Log(buf);
|
||||
|
||||
ScanAndApplyProfile(ctx);
|
||||
|
||||
if (!CalCtx.ReferencePoseIsValid())
|
||||
{
|
||||
CalCtx.Log("Reference device is not tracking\n"); ok = false;
|
||||
}
|
||||
|
||||
if (!CalCtx.TargetPoseIsValid())
|
||||
{
|
||||
CalCtx.Log("Target device is not tracking\n"); ok = false;
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
//ResetAndDisableOffsets(ctx.targetID);
|
||||
ctx.state = CalibrationState::Rotation;
|
||||
ctx.wantedUpdateInterval = 0.0;
|
||||
|
||||
CalCtx.Log("Starting calibration...\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
if (ctx.state != CalibrationState::Continuous) {
|
||||
ctx.state = CalibrationState::None;
|
||||
|
||||
CalCtx.Log("Aborting calibration!\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CollectSample(ctx))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CalCtx.Progress(calibration.SampleCount(), (int)CalCtx.SampleCount());
|
||||
|
||||
while (calibration.SampleCount() > CalCtx.SampleCount()) calibration.ShiftSample();
|
||||
|
||||
if (calibration.SampleCount() >= CalCtx.SampleCount())
|
||||
{
|
||||
LARGE_INTEGER start_time;
|
||||
QueryPerformanceCounter(&start_time);
|
||||
|
||||
bool ok, lerp = false;
|
||||
|
||||
if (CalCtx.state == CalibrationState::Continuous) {
|
||||
CalCtx.messages.clear();
|
||||
calibration.enableStaticRecalibration = CalCtx.enableStaticRecalibration;
|
||||
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()));
|
||||
|
||||
ctx.validProfile = true;
|
||||
SaveProfile(ctx);
|
||||
|
||||
ScanAndApplyProfile(ctx);
|
||||
|
||||
CalCtx.Log("Finished calibration, profile saved\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
CalCtx.Log("Calibration failed.\n");
|
||||
}
|
||||
|
||||
LARGE_INTEGER end_time;
|
||||
QueryPerformanceCounter(&end_time);
|
||||
LARGE_INTEGER freq;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
double duration = (end_time.QuadPart - start_time.QuadPart) / (double)freq.QuadPart;
|
||||
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 < 50; i++) calibration.ShiftSample();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadChaperoneBounds()
|
||||
{
|
||||
vr::VRChaperoneSetup()->RevertWorkingCopy();
|
||||
|
||||
uint32_t quadCount = 0;
|
||||
vr::VRChaperoneSetup()->GetLiveCollisionBoundsInfo(nullptr, &quadCount);
|
||||
|
||||
CalCtx.chaperone.geometry.resize(quadCount);
|
||||
vr::VRChaperoneSetup()->GetLiveCollisionBoundsInfo(&CalCtx.chaperone.geometry[0], &quadCount);
|
||||
vr::VRChaperoneSetup()->GetWorkingStandingZeroPoseToRawTrackingPose(&CalCtx.chaperone.standingCenter);
|
||||
vr::VRChaperoneSetup()->GetWorkingPlayAreaSize(&CalCtx.chaperone.playSpaceSize.v[0], &CalCtx.chaperone.playSpaceSize.v[1]);
|
||||
CalCtx.chaperone.valid = true;
|
||||
}
|
||||
|
||||
void ApplyChaperoneBounds()
|
||||
{
|
||||
vr::VRChaperoneSetup()->RevertWorkingCopy();
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
#pragma once
|
||||
|
||||
#include <Eigen/Core>
|
||||
#include <Eigen/Geometry>
|
||||
#include <Windows.h>
|
||||
#include <openvr.h>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
#include "../Protocol.h"
|
||||
|
||||
enum class CalibrationState
|
||||
{
|
||||
None,
|
||||
Begin,
|
||||
Rotation,
|
||||
Translation,
|
||||
Editing,
|
||||
Continuous,
|
||||
ContinuousStandby,
|
||||
};
|
||||
|
||||
struct StandbyDevice {
|
||||
std::string trackingSystem;
|
||||
std::string model, serial;
|
||||
};
|
||||
|
||||
struct CalibrationContext
|
||||
{
|
||||
CalibrationState state = CalibrationState::None;
|
||||
int32_t referenceID = -1, targetID = -1;
|
||||
|
||||
StandbyDevice targetStandby, referenceStandby;
|
||||
|
||||
Eigen::Vector3d calibratedRotation;
|
||||
Eigen::Vector3d calibratedTranslation;
|
||||
double calibratedScale;
|
||||
|
||||
std::string referenceTrackingSystem;
|
||||
std::string targetTrackingSystem;
|
||||
|
||||
bool enabled = false;
|
||||
bool validProfile = false;
|
||||
bool clearOnLog = false;
|
||||
bool quashTargetInContinuous = false;
|
||||
double timeLastTick = 0, timeLastScan = 0;
|
||||
double wantedUpdateInterval = 1.0;
|
||||
|
||||
float continuousCalibrationThreshold;
|
||||
|
||||
protocol::AlignmentSpeedParams alignmentSpeedParams;
|
||||
bool enableStaticRecalibration;
|
||||
|
||||
Eigen::AffineCompact3d refToTargetPose = Eigen::AffineCompact3d::Identity();
|
||||
bool relativePosCalibrated = false;
|
||||
|
||||
enum Speed
|
||||
{
|
||||
FAST = 0,
|
||||
SLOW = 1,
|
||||
VERY_SLOW = 2
|
||||
};
|
||||
Speed calibrationSpeed = FAST;
|
||||
|
||||
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;
|
||||
|
||||
enableStaticRecalibration = true;
|
||||
}
|
||||
|
||||
struct Chaperone
|
||||
{
|
||||
bool valid = false;
|
||||
bool autoApply = true;
|
||||
std::vector<vr::HmdQuad_t> geometry;
|
||||
vr::HmdMatrix34_t standingCenter;
|
||||
vr::HmdVector2_t playSpaceSize;
|
||||
} chaperone;
|
||||
|
||||
void ClearLogOnMessage() {
|
||||
clearOnLog = true;
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
chaperone.geometry.clear();
|
||||
chaperone.standingCenter = vr::HmdMatrix34_t();
|
||||
chaperone.playSpaceSize = vr::HmdVector2_t();
|
||||
chaperone.valid = false;
|
||||
|
||||
calibratedRotation = Eigen::Vector3d();
|
||||
calibratedTranslation = Eigen::Vector3d();
|
||||
calibratedScale = 1.0;
|
||||
referenceTrackingSystem = "";
|
||||
targetTrackingSystem = "";
|
||||
enabled = false;
|
||||
validProfile = false;
|
||||
refToTargetPose = Eigen::AffineCompact3d::Identity();
|
||||
relativePosCalibrated = true;
|
||||
}
|
||||
|
||||
size_t SampleCount()
|
||||
{
|
||||
switch (calibrationSpeed)
|
||||
{
|
||||
case FAST:
|
||||
return 100;
|
||||
case SLOW:
|
||||
return 250;
|
||||
case VERY_SLOW:
|
||||
return 500;
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
|
||||
struct Message
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
String,
|
||||
Progress
|
||||
} type = String;
|
||||
|
||||
Message(Type type) : type(type) { }
|
||||
|
||||
std::string str;
|
||||
int progress, target;
|
||||
};
|
||||
|
||||
std::deque<Message> messages;
|
||||
|
||||
void Log(const std::string &msg)
|
||||
{
|
||||
if (clearOnLog) {
|
||||
messages.clear();
|
||||
clearOnLog = false;
|
||||
}
|
||||
|
||||
if (messages.empty() || messages.back().type == Message::Progress)
|
||||
messages.push_back(Message(Message::String));
|
||||
|
||||
OutputDebugStringA(msg.c_str());
|
||||
|
||||
messages.back().str += msg;
|
||||
std::cerr << msg;
|
||||
|
||||
while (messages.size() > 15) messages.pop_front();
|
||||
}
|
||||
|
||||
void Progress(int current, int target)
|
||||
{
|
||||
if (messages.empty() || messages.back().type == Message::String)
|
||||
messages.push_back(Message(Message::Progress));
|
||||
|
||||
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;
|
||||
|
||||
void InitCalibrator();
|
||||
void CalibrationTick(double time);
|
||||
void StartCalibration();
|
||||
void StartContinuousCalibration();
|
||||
void EndContinuousCalibration();
|
||||
void LoadChaperoneBounds();
|
||||
void ApplyChaperoneBounds();
|
||||
|
||||
void PushCalibrationApplyTime();
|
||||
void ShowCalibrationDebug(int r, int c);
|
||||
void DebugApplyRandomOffset();
|
||||
@@ -0,0 +1,640 @@
|
||||
#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) {
|
||||
return {
|
||||
(lhs.w * rhs.w) - (lhs.x * rhs.x) - (lhs.y * rhs.y) - (lhs.z * rhs.z),
|
||||
(lhs.w * rhs.x) + (lhs.x * rhs.w) + (lhs.y * rhs.z) - (lhs.z * rhs.y),
|
||||
(lhs.w * rhs.y) + (lhs.y * rhs.w) + (lhs.z * rhs.x) - (lhs.x * rhs.z),
|
||||
(lhs.w * rhs.z) + (lhs.z * rhs.w) + (lhs.x * rhs.y) - (lhs.y * rhs.x)
|
||||
};
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
inline vr::HmdVector3d_t quaternionRotateVector(const vr::HmdQuaternion_t& quat, const double(&vector)[3]) {
|
||||
vr::HmdQuaternion_t vectorQuat = { 0.0, vector[0], vector[1] , vector[2] };
|
||||
vr::HmdQuaternion_t conjugate = { quat.w, -quat.x, -quat.y, -quat.z };
|
||||
auto rotatedVectorQuat = quat * vectorQuat * conjugate;
|
||||
return { rotatedVectorQuat.x, rotatedVectorQuat.y, rotatedVectorQuat.z };
|
||||
}
|
||||
|
||||
inline Eigen::Matrix3d quaternionRotateMatrix(const vr::HmdQuaternion_t& quat) {
|
||||
return Eigen::Quaterniond(quat.w, quat.x, quat.y, quat.z).toRotationMatrix();
|
||||
}
|
||||
|
||||
struct DSample
|
||||
{
|
||||
bool valid;
|
||||
Eigen::Vector3d ref, target;
|
||||
};
|
||||
|
||||
bool StartsWith(const std::string& str, const std::string& prefix)
|
||||
{
|
||||
if (str.length() < prefix.length())
|
||||
return false;
|
||||
|
||||
return str.compare(0, prefix.length(), prefix) == 0;
|
||||
}
|
||||
|
||||
bool EndsWith(const std::string& str, const std::string& suffix)
|
||||
{
|
||||
if (str.length() < suffix.length())
|
||||
return false;
|
||||
|
||||
return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
|
||||
}
|
||||
|
||||
Eigen::Vector3d AxisFromRotationMatrix3(Eigen::Matrix3d rot)
|
||||
{
|
||||
return Eigen::Vector3d(rot(2, 1) - rot(1, 2), rot(0, 2) - rot(2, 0), rot(1, 0) - rot(0, 1));
|
||||
}
|
||||
|
||||
double AngleFromRotationMatrix3(Eigen::Matrix3d rot)
|
||||
{
|
||||
return acos((rot(0, 0) + rot(1, 1) + rot(2, 2) - 1.0) / 2.0);
|
||||
}
|
||||
|
||||
vr::HmdQuaternion_t VRRotationQuat(Eigen::Vector3d eulerdeg)
|
||||
{
|
||||
auto euler = eulerdeg * EIGEN_PI / 180.0;
|
||||
|
||||
Eigen::Quaterniond rotQuat =
|
||||
Eigen::AngleAxisd(euler(0), Eigen::Vector3d::UnitZ()) *
|
||||
Eigen::AngleAxisd(euler(1), Eigen::Vector3d::UnitY()) *
|
||||
Eigen::AngleAxisd(euler(2), Eigen::Vector3d::UnitX());
|
||||
|
||||
vr::HmdQuaternion_t vrRotQuat;
|
||||
vrRotQuat.x = rotQuat.coeffs()[0];
|
||||
vrRotQuat.y = rotQuat.coeffs()[1];
|
||||
vrRotQuat.z = rotQuat.coeffs()[2];
|
||||
vrRotQuat.w = rotQuat.coeffs()[3];
|
||||
return vrRotQuat;
|
||||
}
|
||||
|
||||
vr::HmdVector3d_t VRTranslationVec(Eigen::Vector3d transcm)
|
||||
{
|
||||
auto trans = transcm * 0.01;
|
||||
vr::HmdVector3d_t vrTrans;
|
||||
vrTrans.v[0] = trans[0];
|
||||
vrTrans.v[1] = trans[1];
|
||||
vrTrans.v[2] = trans[2];
|
||||
return vrTrans;
|
||||
}
|
||||
|
||||
DSample DeltaRotationSamples(Sample s1, Sample s2)
|
||||
{
|
||||
// Difference in rotation between samples.
|
||||
auto dref = s1.ref.rot * s2.ref.rot.transpose();
|
||||
auto dtarget = s1.target.rot * s2.target.rot.transpose();
|
||||
|
||||
// When stuck together, the two tracked objects rotate as a pair,
|
||||
// therefore their axes of rotation must be equal between any given pair of samples.
|
||||
DSample ds;
|
||||
ds.ref = AxisFromRotationMatrix3(dref);
|
||||
ds.target = AxisFromRotationMatrix3(dtarget);
|
||||
|
||||
// Reject samples that were too close to each other.
|
||||
auto refA = AngleFromRotationMatrix3(dref);
|
||||
auto targetA = AngleFromRotationMatrix3(dtarget);
|
||||
ds.valid = refA > 0.4 && targetA > 0.4 && ds.ref.norm() > 0.01 && ds.target.norm() > 0.01;
|
||||
|
||||
ds.ref.normalize();
|
||||
ds.target.normalize();
|
||||
return ds;
|
||||
}
|
||||
}
|
||||
|
||||
const double CalibrationCalc::AxisVarianceThreshold = 0.001;
|
||||
void CalibrationCalc::PushSample(const Sample& sample) {
|
||||
m_samples.push_back(sample);
|
||||
}
|
||||
|
||||
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 {
|
||||
std::vector<DSample> deltas;
|
||||
|
||||
for (size_t i = 0; i < m_samples.size(); i++)
|
||||
{
|
||||
for (size_t j = 0; j < i; j++)
|
||||
{
|
||||
auto delta = DeltaRotationSamples(m_samples[i], m_samples[j]);
|
||||
if (delta.valid)
|
||||
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);
|
||||
|
||||
// Kabsch algorithm
|
||||
|
||||
Eigen::MatrixXd refPoints(deltas.size(), 3), targetPoints(deltas.size(), 3);
|
||||
Eigen::Vector3d refCentroid(0, 0, 0), targetCentroid(0, 0, 0);
|
||||
|
||||
for (size_t i = 0; i < deltas.size(); i++)
|
||||
{
|
||||
refPoints.row(i) = deltas[i].ref;
|
||||
refCentroid += deltas[i].ref;
|
||||
|
||||
targetPoints.row(i) = deltas[i].target;
|
||||
targetCentroid += deltas[i].target;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
auto crossCV = refPoints.transpose() * targetPoints;
|
||||
|
||||
Eigen::BDCSVD<Eigen::MatrixXd> bdcsvd;
|
||||
auto svd = bdcsvd.compute(crossCV, Eigen::ComputeThinU | Eigen::ComputeThinV);
|
||||
|
||||
Eigen::Matrix3d i = Eigen::Matrix3d::Identity();
|
||||
if ((svd.matrixU() * svd.matrixV().transpose()).determinant() < 0)
|
||||
{
|
||||
i(2, 2) = -1;
|
||||
}
|
||||
|
||||
Eigen::Matrix3d rot = svd.matrixV() * i * svd.matrixU().transpose();
|
||||
rot.transposeInPlace();
|
||||
|
||||
Eigen::Vector3d euler = rot.eulerAngles(2, 1, 0) * 180.0 / EIGEN_PI;
|
||||
|
||||
//snprintf(buf, sizeof buf, "Calibrated rotation: yaw=%.2f pitch=%.2f roll=%.2f\n", euler[1], euler[2], euler[0]);
|
||||
//CalCtx.Log(buf);
|
||||
return euler;
|
||||
}
|
||||
|
||||
Eigen::Vector3d CalibrationCalc::CalibrateTranslation(const Eigen::Matrix3d &rotation) const
|
||||
{
|
||||
std::vector<std::pair<Eigen::Vector3d, Eigen::Matrix3d>> deltas;
|
||||
|
||||
for (size_t i = 0; i < m_samples.size(); i++)
|
||||
{
|
||||
Sample s_i = m_samples[i];
|
||||
s_i.target.rot = rotation * s_i.target.rot;
|
||||
s_i.target.trans = rotation * s_i.target.trans;
|
||||
|
||||
for (size_t j = 0; j < i; j++)
|
||||
{
|
||||
Sample s_j = m_samples[j];
|
||||
s_j.target.rot = rotation * s_j.target.rot;
|
||||
s_j.target.trans = rotation * s_j.target.trans;
|
||||
|
||||
auto QAi = s_i.ref.rot.transpose();
|
||||
auto QAj = s_j.ref.rot.transpose();
|
||||
auto dQA = QAj - QAi;
|
||||
auto CA = QAj * (s_j.ref.trans - s_j.target.trans) - QAi * (s_i.ref.trans - s_i.target.trans);
|
||||
deltas.push_back(std::make_pair(CA, dQA));
|
||||
|
||||
auto QBi = s_i.target.rot.transpose();
|
||||
auto QBj = s_j.target.rot.transpose();
|
||||
auto dQB = QBj - QBi;
|
||||
auto CB = QBj * (s_j.ref.trans - s_j.target.trans) - QBi * (s_i.ref.trans - s_i.target.trans);
|
||||
deltas.push_back(std::make_pair(CB, dQB));
|
||||
}
|
||||
}
|
||||
|
||||
Eigen::VectorXd constants(deltas.size() * 3);
|
||||
Eigen::MatrixXd coefficients(deltas.size() * 3, 3);
|
||||
|
||||
for (size_t i = 0; i < deltas.size(); i++)
|
||||
{
|
||||
for (int axis = 0; axis < 3; axis++)
|
||||
{
|
||||
constants(i * 3 + axis) = deltas[i].first(axis);
|
||||
coefficients.row(i * 3 + axis) = deltas[i].second.row(axis);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return trans;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
Pose ApplyTransform(const Pose& originalPose, const Eigen::AffineCompact3d& transform) {
|
||||
Pose pose(originalPose);
|
||||
pose.rot = transform.rotation() * pose.rot;
|
||||
pose.trans = transform * pose.trans;
|
||||
return pose;
|
||||
}
|
||||
|
||||
Pose ApplyTransform(const Pose & originalPose, const Eigen::Vector3d & vrTrans, const Eigen::Matrix3d & rotMat) {
|
||||
Pose pose(originalPose);
|
||||
pose.rot = rotMat * pose.rot;
|
||||
pose.trans = vrTrans + (rotMat * pose.trans);
|
||||
return pose;
|
||||
}
|
||||
}
|
||||
|
||||
Eigen::AffineCompact3d CalibrationCalc::ComputeCalibration() const {
|
||||
Eigen::Vector3d rotation = CalibrateRotation();
|
||||
Eigen::Matrix3d rotationMat = quaternionRotateMatrix(VRRotationQuat(rotation));
|
||||
Eigen::Vector3d translation = CalibrateTranslation(rotationMat);
|
||||
|
||||
Eigen::AffineCompact3d rot(rotationMat);
|
||||
Eigen::Translation3d trans(translation);
|
||||
|
||||
return trans * rot;
|
||||
}
|
||||
|
||||
|
||||
|
||||
double CalibrationCalc::RetargetingErrorRMS(
|
||||
const Eigen::Vector3d& hmdToTargetPos,
|
||||
const Eigen::AffineCompact3d& calibration
|
||||
) const {
|
||||
double errorAccum = 0;
|
||||
int sampleCount = 0;
|
||||
|
||||
for (auto& sample : m_samples) {
|
||||
if (!sample.valid) continue;
|
||||
|
||||
// Apply transformation
|
||||
const auto updatedPose = ApplyTransform(sample.target, calibration);
|
||||
|
||||
const Eigen::Vector3d hmdPoseSpace = sample.ref.rot * hmdToTargetPos + sample.ref.trans;
|
||||
|
||||
// Compute error term
|
||||
double error = (updatedPose.trans - hmdPoseSpace).squaredNorm();
|
||||
errorAccum += error;
|
||||
sampleCount++;
|
||||
}
|
||||
|
||||
return sqrt(errorAccum / sampleCount);
|
||||
}
|
||||
|
||||
Eigen::Vector3d CalibrationCalc::ComputeRefToTargetOffset(const Eigen::AffineCompact3d& calibration) const {
|
||||
Eigen::Vector3d accum = Eigen::Vector3d::Zero();
|
||||
int sampleCount = 0;
|
||||
|
||||
for (auto& sample : m_samples) {
|
||||
if (!sample.valid) continue;
|
||||
|
||||
// Apply transformation
|
||||
const auto updatedPose = ApplyTransform(sample.target, calibration);
|
||||
|
||||
// Now move the transform from world to HMD space
|
||||
const auto hmdOriginPos = updatedPose.trans - sample.ref.trans;
|
||||
const auto hmdSpace = sample.ref.rot.inverse() * hmdOriginPos;
|
||||
|
||||
accum += hmdSpace;
|
||||
sampleCount++;
|
||||
}
|
||||
|
||||
accum /= sampleCount;
|
||||
|
||||
return accum;
|
||||
}
|
||||
|
||||
Eigen::Vector4d CalibrationCalc::ComputeAxisVariance(
|
||||
const Eigen::AffineCompact3d& calibration
|
||||
) const {
|
||||
// We want to determine if the user rotated in enough axis to find a unique solution.
|
||||
// It's sufficient to rotate in two axis - this is because once we constrain the mapping
|
||||
// of those two orthogonal basis vectors, the third is determined by the cross product of
|
||||
// those two basis vectors. So, the question we then have to answer is - after accounting for
|
||||
// translational movement of the HMD itself, are we too close to having only moved on a plane?
|
||||
|
||||
// To determine this, we perform primary component analysis on the rotation quaternions themselves.
|
||||
// Since an angle axis quaternion is defined as the sum of Qidentity*cos(angle/2) + Qaxis*sin(angle/2),
|
||||
// we expect that rotations around a single axis will have two primary components: One corresponding
|
||||
// to the identity component, and one to the axis component. Thus, we check the variance (eigenvalue) of
|
||||
// the third primary component to see if we've moved in two axis.
|
||||
std::ostringstream dbgStream;
|
||||
|
||||
std::vector<Eigen::Vector4d> points;
|
||||
|
||||
Eigen::Vector4d mean = Eigen::Vector4d::Zero();
|
||||
|
||||
for (auto& sample : m_samples) {
|
||||
if (!sample.valid) continue;
|
||||
|
||||
auto q = Eigen::Quaterniond(sample.target.rot);
|
||||
auto point = Eigen::Vector4d(q.w(), q.x(), q.y(), q.z());
|
||||
mean += point;
|
||||
|
||||
points.push_back(point);
|
||||
}
|
||||
mean /= (double) points.size();
|
||||
|
||||
// Compute covariance matrix
|
||||
Eigen::Matrix4d covMatrix = Eigen::Matrix4d::Zero();
|
||||
|
||||
for (auto& point : points) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
covMatrix(i, j) += (point(i) - mean(i)) * (point(j) - mean(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
covMatrix /= (double) points.size();
|
||||
|
||||
Eigen::SelfAdjointEigenSolver<Eigen::Matrix4d> solver;
|
||||
solver.compute(covMatrix);
|
||||
|
||||
return solver.eigenvalues();
|
||||
}
|
||||
|
||||
bool CalibrationCalc::ValidateCalibration(const Eigen::AffineCompact3d &calibration, double *error, Eigen::Vector3d *posOffsetV) {
|
||||
bool ok = true;
|
||||
|
||||
const auto posOffset = ComputeRefToTargetOffset(calibration);
|
||||
|
||||
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);
|
||||
if (rmsError > 0.1) ok = false;
|
||||
|
||||
if (error) *error = rmsError;
|
||||
|
||||
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_isValid = true;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
CalCtx.Log("Not updating: Low-quality calibration result\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Metrics::posOffset_lastSample.Push(hmdSpace * 1000);
|
||||
}
|
||||
|
||||
bool CalibrationCalc::ComputeIncremental(bool &lerp, double threshold) {
|
||||
|
||||
|
||||
Metrics::RecordTimestamp();
|
||||
|
||||
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.015 || m_relativePosCalibrated && relPoseError < 0.035) {
|
||||
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 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 if (m_relativePosCalibrated) {
|
||||
CalCtx.Log("Applying updated transformation...");
|
||||
} else {
|
||||
CalCtx.Log("Applying temporary transformation...");
|
||||
}
|
||||
|
||||
m_isValid = true;
|
||||
m_estimatedTransformation = calibration;
|
||||
m_axisVariance = newVariance;
|
||||
|
||||
if (!usingRelPose) {
|
||||
m_refToTargetPose = EstimateRefToTargetPose(m_estimatedTransformation);
|
||||
}
|
||||
|
||||
Metrics::calibrationApplied.Push(!usingRelPose);
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
|
||||
#include <Eigen/Dense>
|
||||
#include <openvr.h>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
|
||||
struct Pose
|
||||
{
|
||||
Eigen::Matrix3d rot;
|
||||
Eigen::Vector3d trans;
|
||||
|
||||
Pose() { }
|
||||
Pose(const Eigen::AffineCompact3d& transform) {
|
||||
rot = transform.rotation();
|
||||
trans = transform.translation();
|
||||
}
|
||||
|
||||
Pose(vr::HmdMatrix34_t hmdMatrix)
|
||||
{
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
rot(i, j) = hmdMatrix.m[i][j];
|
||||
}
|
||||
}
|
||||
trans = Eigen::Vector3d(hmdMatrix.m[0][3], hmdMatrix.m[1][3], hmdMatrix.m[2][3]);
|
||||
}
|
||||
Pose(vr::HmdQuaternion_t rot, const double *trans) {
|
||||
this->rot = Eigen::Matrix3d(Eigen::Quaterniond(rot.w, rot.x, rot.y, rot.z));
|
||||
this->trans = Eigen::Vector3d(trans[0], trans[1], trans[2]);
|
||||
}
|
||||
Pose(double x, double y, double z) : trans(Eigen::Vector3d(x, y, z)) { }
|
||||
|
||||
Eigen::Matrix4d ToAffine() const {
|
||||
Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity();
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
matrix(i, j) = rot(i, j);
|
||||
}
|
||||
matrix(i, 3) = trans(i);
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
};
|
||||
|
||||
struct Sample
|
||||
{
|
||||
Pose ref, target;
|
||||
bool valid;
|
||||
Sample() : valid(false) { }
|
||||
Sample(Pose ref, Pose target) : valid(true), ref(ref), target(target) { }
|
||||
};
|
||||
|
||||
class CalibrationCalc {
|
||||
public:
|
||||
static const double AxisVarianceThreshold;
|
||||
|
||||
bool enableStaticRecalibration;
|
||||
|
||||
const Eigen::AffineCompact3d Transformation() const
|
||||
{
|
||||
return m_estimatedTransformation;
|
||||
}
|
||||
|
||||
const Eigen::Vector3d EulerRotation() const {
|
||||
auto rot = m_estimatedTransformation.rotation();
|
||||
return rot.eulerAngles(2, 1, 0) * 180.0 / EIGEN_PI;
|
||||
}
|
||||
|
||||
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, double threshold);
|
||||
|
||||
size_t SampleCount() const {
|
||||
return m_samples.size();
|
||||
}
|
||||
|
||||
void ShiftSample() {
|
||||
if (!m_samples.empty()) m_samples.pop_front();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Eigen::AffineCompact3d ComputeCalibration() const;
|
||||
|
||||
double RetargetingErrorRMS(const Eigen::Vector3d& hmdToTargetPos, const Eigen::AffineCompact3d& calibration) const;
|
||||
Eigen::Vector3d ComputeRefToTargetOffset(const Eigen::AffineCompact3d& calibration) const;
|
||||
|
||||
Eigen::Vector4d ComputeAxisVariance(const Eigen::AffineCompact3d& calibration) const;
|
||||
|
||||
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,383 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
if (lastMouseX != initMouseX) {
|
||||
RequestImmediateRedraw();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
#include "stdafx.h"
|
||||
#include "Configuration.h"
|
||||
|
||||
#include <picojson.h>
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
|
||||
static picojson::array FloatArray(const float *buf, int numFloats)
|
||||
{
|
||||
picojson::array arr;
|
||||
|
||||
for (int i = 0; i < numFloats; i++)
|
||||
arr.push_back(picojson::value(double(buf[i])));
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
static void LoadFloatArray(const picojson::value &obj, float *buf, int numFloats)
|
||||
{
|
||||
if (!obj.is<picojson::array>())
|
||||
throw std::runtime_error("expected array, got " + obj.to_str());
|
||||
|
||||
auto &arr = obj.get<picojson::array>();
|
||||
if (arr.size() != numFloats)
|
||||
throw std::runtime_error("wrong buffer size");
|
||||
|
||||
for (int i = 0; i < numFloats; i++)
|
||||
buf[i] = (float) arr[i].get<double>();
|
||||
}
|
||||
|
||||
static void LoadStandby(StandbyDevice& device, picojson::value& value) {
|
||||
if (!value.is<picojson::object>()) return;
|
||||
auto& obj = value.get<picojson::object>();
|
||||
|
||||
const auto &system = obj["tracking_system"];
|
||||
if (system.is<std::string>()) device.trackingSystem = system.get<std::string>();
|
||||
|
||||
const auto& model = obj["model"];
|
||||
if (model.is<std::string>()) device.model = model.get<std::string>();
|
||||
|
||||
const auto& serial = obj["serial"];
|
||||
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;
|
||||
std::string err = picojson::parse(v, stream);
|
||||
if (!err.empty())
|
||||
throw std::runtime_error(err);
|
||||
|
||||
auto arr = v.get<picojson::array>();
|
||||
if (arr.size() < 1)
|
||||
throw std::runtime_error("no profiles in file");
|
||||
|
||||
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>();
|
||||
ctx.calibratedRotation(1) = obj["yaw"].get<double>();
|
||||
ctx.calibratedRotation(2) = obj["pitch"].get<double>();
|
||||
ctx.calibratedTranslation(0) = obj["x"].get<double>();
|
||||
ctx.calibratedTranslation(1) = obj["y"].get<double>();
|
||||
ctx.calibratedTranslation(2) = obj["z"].get<double>();
|
||||
LoadStandby(ctx.referenceStandby, obj["reference_device"]);
|
||||
LoadStandby(ctx.targetStandby, obj["target_device"]);
|
||||
if (obj["autostart_continuous_calibration"].evaluate_as_boolean()) {
|
||||
ctx.state = CalibrationState::ContinuousStandby;
|
||||
}
|
||||
ctx.quashTargetInContinuous = obj["quash_target_in_continuous"].evaluate_as_boolean();
|
||||
|
||||
if (obj["scale"].is<double>())
|
||||
ctx.calibratedScale = obj["scale"].get<double>();
|
||||
else
|
||||
ctx.calibratedScale = 1.0;
|
||||
|
||||
if (obj["calibration_speed"].is<double>())
|
||||
ctx.calibrationSpeed = (CalibrationContext::Speed)(int) obj["calibration_speed"].get<double>();
|
||||
|
||||
if (obj["chaperone"].is<picojson::object>())
|
||||
{
|
||||
auto chaperone = obj["chaperone"].get<picojson::object>();
|
||||
ctx.chaperone.autoApply = chaperone["auto_apply"].get<bool>();
|
||||
|
||||
LoadFloatArray(chaperone["play_space_size"], ctx.chaperone.playSpaceSize.v, 2);
|
||||
|
||||
LoadFloatArray(
|
||||
chaperone["standing_center"],
|
||||
(float *) ctx.chaperone.standingCenter.m,
|
||||
sizeof(ctx.chaperone.standingCenter.m) / sizeof(float)
|
||||
);
|
||||
|
||||
if (!chaperone["geometry"].is<picojson::array>())
|
||||
throw std::runtime_error("chaperone geometry is not an array");
|
||||
|
||||
auto &geometry = chaperone["geometry"].get<picojson::array>();
|
||||
|
||||
if (geometry.size() > 0)
|
||||
{
|
||||
ctx.chaperone.geometry.resize(geometry.size() * sizeof(float) / sizeof(ctx.chaperone.geometry[0]));
|
||||
LoadFloatArray(chaperone["geometry"], (float *) ctx.chaperone.geometry.data(), geometry.size());
|
||||
|
||||
ctx.chaperone.valid = true;
|
||||
}
|
||||
}
|
||||
if (obj["relative_pos_calibrated"].is<bool>()) {
|
||||
ctx.relativePosCalibrated = obj["relative_pos_calibrated"].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;
|
||||
}
|
||||
|
||||
|
||||
static void WriteStandby(StandbyDevice& device, picojson::value& value) {
|
||||
auto obj = picojson::object();
|
||||
|
||||
obj["tracking_system"].set<std::string>(device.trackingSystem);
|
||||
obj["model"].set<std::string>(device.model);
|
||||
obj["serial"].set<std::string>(device.serial);
|
||||
|
||||
value.set<picojson::object>(obj);
|
||||
}
|
||||
|
||||
|
||||
static void WriteProfile(CalibrationContext &ctx, std::ostream &out)
|
||||
{
|
||||
if (!ctx.validProfile)
|
||||
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));
|
||||
profile["yaw"].set<double>(ctx.calibratedRotation(1));
|
||||
profile["pitch"].set<double>(ctx.calibratedRotation(2));
|
||||
profile["x"].set<double>(ctx.calibratedTranslation(0));
|
||||
profile["y"].set<double>(ctx.calibratedTranslation(1));
|
||||
profile["z"].set<double>(ctx.calibratedTranslation(2));
|
||||
profile["scale"].set<double>(ctx.calibratedScale);
|
||||
WriteStandby(ctx.referenceStandby, profile["reference_device"]);
|
||||
WriteStandby(ctx.targetStandby, profile["target_device"]);
|
||||
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);
|
||||
|
||||
double speed = (int) ctx.calibrationSpeed;
|
||||
profile["calibration_speed"].set<double>(speed);
|
||||
|
||||
if (ctx.chaperone.valid)
|
||||
{
|
||||
picojson::object chaperone;
|
||||
chaperone["auto_apply"].set<bool>(ctx.chaperone.autoApply);
|
||||
chaperone["play_space_size"].set<picojson::array>(FloatArray(ctx.chaperone.playSpaceSize.v, 2));
|
||||
|
||||
chaperone["standing_center"].set<picojson::array>(FloatArray(
|
||||
(float *) ctx.chaperone.standingCenter.m,
|
||||
sizeof(ctx.chaperone.standingCenter.m) / sizeof(float)
|
||||
));
|
||||
|
||||
chaperone["geometry"].set<picojson::array>(FloatArray(
|
||||
(float *) ctx.chaperone.geometry.data(),
|
||||
sizeof(ctx.chaperone.geometry[0]) / sizeof(float) * ctx.chaperone.geometry.size()
|
||||
));
|
||||
|
||||
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["relative_transform"].set<picojson::object>(refToTarget);
|
||||
|
||||
picojson::value profileV;
|
||||
profileV.set<picojson::object>(profile);
|
||||
|
||||
picojson::array profiles;
|
||||
profiles.push_back(profileV);
|
||||
|
||||
picojson::value profilesV;
|
||||
profilesV.set<picojson::array>(profiles);
|
||||
|
||||
out << profilesV.serialize(true);
|
||||
}
|
||||
|
||||
static void LogRegistryResult(LSTATUS result)
|
||||
{
|
||||
char *message;
|
||||
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, result, LANG_USER_DEFAULT, (LPSTR)&message, 0, NULL);
|
||||
std::cerr << "Opening registry key: " << message << std::endl;
|
||||
}
|
||||
|
||||
static const char *RegistryKey = "Software\\OpenVR-SpaceCalibrator";
|
||||
|
||||
static std::string ReadRegistryKey()
|
||||
{
|
||||
DWORD size = 0;
|
||||
auto result = RegGetValueA(HKEY_CURRENT_USER_LOCAL_SETTINGS, RegistryKey, "Config", RRF_RT_REG_SZ, 0, 0, &size);
|
||||
if (result != ERROR_SUCCESS)
|
||||
{
|
||||
LogRegistryResult(result);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string str;
|
||||
str.resize(size);
|
||||
|
||||
result = RegGetValueA(HKEY_CURRENT_USER_LOCAL_SETTINGS, RegistryKey, "Config", RRF_RT_REG_SZ, 0, &str[0], &size);
|
||||
if (result != ERROR_SUCCESS)
|
||||
{
|
||||
LogRegistryResult(result);
|
||||
return "";
|
||||
}
|
||||
|
||||
str.resize(size - 1);
|
||||
return str;
|
||||
}
|
||||
|
||||
static void WriteRegistryKey(std::string str)
|
||||
{
|
||||
HKEY hkey;
|
||||
auto result = RegCreateKeyExA(HKEY_CURRENT_USER_LOCAL_SETTINGS, RegistryKey, 0, REG_NONE, 0, KEY_ALL_ACCESS, 0, &hkey, 0);
|
||||
if (result != ERROR_SUCCESS)
|
||||
{
|
||||
LogRegistryResult(result);
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD size = str.size() + 1;
|
||||
|
||||
result = RegSetValueExA(hkey, "Config", 0, REG_SZ, reinterpret_cast<const BYTE*>(str.c_str()), size);
|
||||
if (result != ERROR_SUCCESS)
|
||||
LogRegistryResult(result);
|
||||
|
||||
RegCloseKey(hkey);
|
||||
}
|
||||
|
||||
void LoadProfile(CalibrationContext &ctx)
|
||||
{
|
||||
ctx.validProfile = false;
|
||||
|
||||
auto str = ReadRegistryKey();
|
||||
if (str == "")
|
||||
{
|
||||
std::cout << "Profile is empty" << std::endl;
|
||||
ctx.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::stringstream io(str);
|
||||
ParseProfile(ctx, io);
|
||||
std::cout << "Loaded profile" << std::endl;
|
||||
}
|
||||
catch (const std::runtime_error &e)
|
||||
{
|
||||
std::cerr << "Error loading profile: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void SaveProfile(CalibrationContext &ctx)
|
||||
{
|
||||
std::cout << "Saving profile to registry" << std::endl;
|
||||
|
||||
std::stringstream io;
|
||||
WriteProfile(ctx, io);
|
||||
WriteRegistryKey(io.str());
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "Calibration.h"
|
||||
|
||||
void LoadProfile(CalibrationContext &ctx);
|
||||
void SaveProfile(CalibrationContext &ctx);
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
const unsigned int DroidSans_compressed_size = 134345;
|
||||
extern const unsigned int DroidSans_compressed_data[134348 / 4];
|
||||
@@ -0,0 +1 @@
|
||||
#pragma once
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "stdafx.h"
|
||||
#include "IPCClient.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
static std::string LastErrorString(DWORD lastError)
|
||||
{
|
||||
LPSTR buffer = nullptr;
|
||||
size_t size = FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, lastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, NULL
|
||||
);
|
||||
|
||||
std::string message(buffer, size);
|
||||
LocalFree(buffer);
|
||||
return message;
|
||||
}
|
||||
|
||||
IPCClient::~IPCClient()
|
||||
{
|
||||
if (pipe && pipe != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(pipe);
|
||||
}
|
||||
|
||||
void IPCClient::Connect()
|
||||
{
|
||||
LPTSTR pipeName = TEXT(OPENVR_SPACECALIBRATOR_PIPE_NAME);
|
||||
|
||||
WaitNamedPipe(pipeName, 1000);
|
||||
pipe = CreateFile(pipeName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
|
||||
|
||||
if (pipe == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
throw std::runtime_error("Space Calibrator driver unavailable. Make sure SteamVR is running, and the Space Calibrator addon is enabled in SteamVR settings.");
|
||||
}
|
||||
|
||||
DWORD mode = PIPE_READMODE_MESSAGE;
|
||||
if (!SetNamedPipeHandleState(pipe, &mode, 0, 0))
|
||||
{
|
||||
throw std::runtime_error("Couldn't set pipe mode. Error: " + LastErrorString(GetLastError()));
|
||||
}
|
||||
|
||||
auto response = SendBlocking(protocol::Request(protocol::RequestHandshake));
|
||||
if (response.type != protocol::ResponseHandshake || response.protocol.version != protocol::Version)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Incorrect driver version installed, try reinstalling OpenVR-SpaceCalibrator. (Client: " +
|
||||
std::to_string(protocol::Version) +
|
||||
", Driver: " +
|
||||
std::to_string(response.protocol.version) +
|
||||
")"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protocol::Response IPCClient::SendBlocking(const protocol::Request &request)
|
||||
{
|
||||
Send(request);
|
||||
return Receive();
|
||||
}
|
||||
|
||||
void IPCClient::Send(const protocol::Request &request)
|
||||
{
|
||||
DWORD bytesWritten;
|
||||
BOOL success = WriteFile(pipe, &request, sizeof request, &bytesWritten, 0);
|
||||
if (!success)
|
||||
{
|
||||
throw std::runtime_error("Error writing IPC request. Error: " + LastErrorString(GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
protocol::Response IPCClient::Receive()
|
||||
{
|
||||
protocol::Response response(protocol::ResponseInvalid);
|
||||
DWORD bytesRead;
|
||||
|
||||
BOOL success = ReadFile(pipe, &response, sizeof response, &bytesRead, 0);
|
||||
if (!success)
|
||||
{
|
||||
DWORD lastError = GetLastError();
|
||||
if (lastError != ERROR_MORE_DATA)
|
||||
{
|
||||
throw std::runtime_error("Error reading IPC response. Error: " + LastErrorString(lastError));
|
||||
}
|
||||
}
|
||||
|
||||
if (bytesRead != sizeof response)
|
||||
{
|
||||
throw std::runtime_error("Invalid IPC response with size " + std::to_string(bytesRead));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Protocol.h"
|
||||
|
||||
class IPCClient
|
||||
{
|
||||
public:
|
||||
~IPCClient();
|
||||
|
||||
void Connect();
|
||||
protocol::Response SendBlocking(const protocol::Request &request);
|
||||
|
||||
void Send(const protocol::Request &request);
|
||||
protocol::Response Receive();
|
||||
|
||||
private:
|
||||
HANDLE pipe = INVALID_HANDLE_VALUE;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
#pragma once
|
||||
@@ -0,0 +1,533 @@
|
||||
#include "stdafx.h"
|
||||
#include "Calibration.h"
|
||||
#include "Configuration.h"
|
||||
#include "EmbeddedFiles.h"
|
||||
#include "UserInterface.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#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>
|
||||
|
||||
#pragma comment(linker,"\"/manifestdependency:type='win32' \
|
||||
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
|
||||
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
|
||||
|
||||
#define OPENVR_APPLICATION_KEY "pushrax.SpaceCalibrator"
|
||||
|
||||
extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
|
||||
extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;
|
||||
|
||||
void CreateConsole()
|
||||
{
|
||||
static bool created = false;
|
||||
if (!created)
|
||||
{
|
||||
AllocConsole();
|
||||
FILE *file = nullptr;
|
||||
freopen_s(&file, "CONIN$", "r", stdin);
|
||||
freopen_s(&file, "CONOUT$", "w", stdout);
|
||||
freopen_s(&file, "CONOUT$", "w", stderr);
|
||||
created = true;
|
||||
}
|
||||
}
|
||||
|
||||
//#define DEBUG_LOGS
|
||||
|
||||
void GLFWErrorCallback(int error, const char* description)
|
||||
{
|
||||
fprintf(stderr, "GLFW Error %d: %s\n", error, description);
|
||||
}
|
||||
|
||||
void openGLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam)
|
||||
{
|
||||
fprintf(stderr, "OpenGL Debug %u: %.*s\n", id, length, message);
|
||||
}
|
||||
|
||||
static void HandleCommandLine(LPWSTR lpCmdLine);
|
||||
|
||||
static GLFWwindow *glfwWindow = nullptr;
|
||||
static vr::VROverlayHandle_t overlayMainHandle = 0, overlayThumbnailHandle = 0;
|
||||
static GLuint fboHandle = 0, fboTextureHandle = 0;
|
||||
static int fboTextureWidth = 0, fboTextureHeight = 0;
|
||||
|
||||
static char cwd[MAX_PATH];
|
||||
|
||||
void CreateGLFWWindow()
|
||||
{
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
glfwWindowHint(GLFW_RESIZABLE, false);
|
||||
|
||||
#ifdef DEBUG_LOGS
|
||||
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
|
||||
#endif
|
||||
|
||||
fboTextureWidth = 1200;
|
||||
fboTextureHeight = 800;
|
||||
|
||||
glfwWindow = glfwCreateWindow(fboTextureWidth, fboTextureHeight, "OpenVR-SpaceCalibrator", NULL, NULL);
|
||||
if (!glfwWindow)
|
||||
throw std::runtime_error("Failed to create window");
|
||||
|
||||
glfwMakeContextCurrent(glfwWindow);
|
||||
glfwSwapInterval(1);
|
||||
gl3wInit();
|
||||
|
||||
glfwIconifyWindow(glfwWindow);
|
||||
|
||||
#ifdef DEBUG_LOGS
|
||||
glDebugMessageCallback(openGLDebugCallback, nullptr);
|
||||
glEnable(GL_DEBUG_OUTPUT);
|
||||
#endif
|
||||
|
||||
ImGui::CreateContext();
|
||||
ImPlot::CreateContext();
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||
io.IniFilename = nullptr;
|
||||
io.Fonts->AddFontFromMemoryCompressedTTF(DroidSans_compressed_data, DroidSans_compressed_size, 24.0f);
|
||||
|
||||
ImGui_ImplGlfw_InitForOpenGL(glfwWindow, true);
|
||||
ImGui_ImplOpenGL3_Init("#version 330");
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
glGenTextures(1, &fboTextureHandle);
|
||||
glBindTexture(GL_TEXTURE_2D, fboTextureHandle);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, fboTextureWidth, fboTextureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
glGenFramebuffers(1, &fboHandle);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fboHandle);
|
||||
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, fboTextureHandle, 0);
|
||||
|
||||
GLenum drawBuffers[1] = { GL_COLOR_ATTACHMENT0 };
|
||||
glDrawBuffers(1, drawBuffers);
|
||||
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
throw std::runtime_error("OpenGL framebuffer incomplete");
|
||||
}
|
||||
}
|
||||
|
||||
void TryCreateVROverlay()
|
||||
{
|
||||
if (overlayMainHandle || !vr::VROverlay())
|
||||
return;
|
||||
|
||||
vr::VROverlayError error = vr::VROverlay()->CreateDashboardOverlay(
|
||||
"pushrax.SpaceCalibrator", "Space Cal",
|
||||
&overlayMainHandle, &overlayThumbnailHandle
|
||||
);
|
||||
|
||||
if (error == vr::VROverlayError_KeyInUse)
|
||||
{
|
||||
throw std::runtime_error("Another instance of OpenVR Space Calibrator is already running");
|
||||
}
|
||||
else if (error != vr::VROverlayError_None)
|
||||
{
|
||||
throw std::runtime_error("Error creating VR overlay: " + std::string(vr::VROverlay()->GetOverlayErrorNameFromEnum(error)));
|
||||
}
|
||||
|
||||
vr::VROverlay()->SetOverlayWidthInMeters(overlayMainHandle, 3.0f);
|
||||
vr::VROverlay()->SetOverlayInputMethod(overlayMainHandle, vr::VROverlayInputMethod_Mouse);
|
||||
vr::VROverlay()->SetOverlayFlag(overlayMainHandle, vr::VROverlayFlags_SendVRDiscreteScrollEvents, true);
|
||||
|
||||
std::string iconPath = cwd;
|
||||
iconPath += "\\icon.png";
|
||||
vr::VROverlay()->SetOverlayFromFile(overlayThumbnailHandle, iconPath.c_str());
|
||||
}
|
||||
|
||||
void ActivateMultipleDrivers()
|
||||
{
|
||||
vr::EVRSettingsError vrSettingsError;
|
||||
bool enabled = vr::VRSettings()->GetBool(vr::k_pch_SteamVR_Section, vr::k_pch_SteamVR_ActivateMultipleDrivers_Bool, &vrSettingsError);
|
||||
|
||||
if (vrSettingsError != vr::VRSettingsError_None)
|
||||
{
|
||||
std::string err = "Could not read \"" + std::string(vr::k_pch_SteamVR_ActivateMultipleDrivers_Bool) + "\" setting: "
|
||||
+ vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
|
||||
|
||||
throw std::runtime_error(err);
|
||||
}
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
vr::VRSettings()->SetBool(vr::k_pch_SteamVR_Section, vr::k_pch_SteamVR_ActivateMultipleDrivers_Bool, true, &vrSettingsError);
|
||||
if (vrSettingsError != vr::VRSettingsError_None)
|
||||
{
|
||||
std::string err = "Could not set \"" + std::string(vr::k_pch_SteamVR_ActivateMultipleDrivers_Bool) + "\" setting: "
|
||||
+ vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
|
||||
|
||||
throw std::runtime_error(err);
|
||||
}
|
||||
|
||||
std::cerr << "Enabled \"" << vr::k_pch_SteamVR_ActivateMultipleDrivers_Bool << "\" setting" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "\"" << vr::k_pch_SteamVR_ActivateMultipleDrivers_Bool << "\" setting previously enabled" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void InitVR()
|
||||
{
|
||||
auto initError = vr::VRInitError_None;
|
||||
vr::VR_Init(&initError, vr::VRApplication_Other);
|
||||
if (initError != vr::VRInitError_None)
|
||||
{
|
||||
auto error = vr::VR_GetVRInitErrorAsEnglishDescription(initError);
|
||||
throw std::runtime_error("OpenVR error:" + std::string(error));
|
||||
}
|
||||
|
||||
if (!vr::VR_IsInterfaceVersionValid(vr::IVRSystem_Version))
|
||||
{
|
||||
throw std::runtime_error("OpenVR error: Outdated IVRSystem_Version");
|
||||
}
|
||||
else if (!vr::VR_IsInterfaceVersionValid(vr::IVRSettings_Version))
|
||||
{
|
||||
throw std::runtime_error("OpenVR error: Outdated IVRSettings_Version");
|
||||
}
|
||||
else if (!vr::VR_IsInterfaceVersionValid(vr::IVROverlay_Version))
|
||||
{
|
||||
throw std::runtime_error("OpenVR error: Outdated IVROverlay_Version");
|
||||
}
|
||||
|
||||
ActivateMultipleDrivers();
|
||||
}
|
||||
|
||||
static char textBuf[0x400];
|
||||
|
||||
static bool immediateRedraw;
|
||||
void RequestImmediateRedraw() {
|
||||
immediateRedraw = true;
|
||||
}
|
||||
|
||||
void RunLoop()
|
||||
{
|
||||
while (!glfwWindowShouldClose(glfwWindow))
|
||||
{
|
||||
TryCreateVROverlay();
|
||||
|
||||
double time = glfwGetTime();
|
||||
CalibrationTick(time);
|
||||
|
||||
bool dashboardVisible = false;
|
||||
int width, height;
|
||||
glfwGetFramebufferSize(glfwWindow, &width, &height);
|
||||
|
||||
if (overlayMainHandle && vr::VROverlay())
|
||||
{
|
||||
auto &io = ImGui::GetIO();
|
||||
dashboardVisible = vr::VROverlay()->IsActiveDashboardOverlay(overlayMainHandle);
|
||||
|
||||
static bool keyboardOpen = false, keyboardJustClosed = false;
|
||||
|
||||
// After closing the keyboard, this code waits one frame for ImGui to pick up the new text from SetActiveText
|
||||
// before clearing the active widget. Then it waits another frame before allowing the keyboard to open again,
|
||||
// otherwise it will do so instantly since WantTextInput is still true on the second frame.
|
||||
if (keyboardJustClosed && keyboardOpen)
|
||||
{
|
||||
ImGui::ClearActiveID();
|
||||
keyboardOpen = false;
|
||||
}
|
||||
else if (keyboardJustClosed)
|
||||
{
|
||||
keyboardJustClosed = false;
|
||||
}
|
||||
else if (!io.WantTextInput)
|
||||
{
|
||||
// User might close the keyboard without hitting Done, so we unset the flag to allow it to open again.
|
||||
keyboardOpen = false;
|
||||
}
|
||||
else if (io.WantTextInput && !keyboardOpen && !keyboardJustClosed)
|
||||
{
|
||||
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
|
||||
|
||||
vr::VROverlay()->ShowKeyboardForOverlay(
|
||||
overlayMainHandle, vr::k_EGamepadTextInputModeNormal, vr::k_EGamepadTextInputLineModeSingleLine,
|
||||
unFlags, "Space Calibrator Overlay", sizeof textBuf, textBuf, 0
|
||||
);
|
||||
keyboardOpen = true;
|
||||
}
|
||||
|
||||
vr::VREvent_t vrEvent;
|
||||
while (vr::VROverlay()->PollNextOverlayEvent(overlayMainHandle, &vrEvent, sizeof(vrEvent)))
|
||||
{
|
||||
switch (vrEvent.eventType) {
|
||||
case vr::VREvent_MouseMove:
|
||||
io.AddMousePosEvent(vrEvent.data.mouse.x, vrEvent.data.mouse.y);
|
||||
break;
|
||||
case vr::VREvent_MouseButtonDown:
|
||||
io.AddMouseButtonEvent(vrEvent.data.mouse.button == vr::VRMouseButton_Left ? 0 : 1, true);
|
||||
break;
|
||||
case vr::VREvent_MouseButtonUp:
|
||||
io.AddMouseButtonEvent(vrEvent.data.mouse.button == vr::VRMouseButton_Left ? 0 : 1, false);
|
||||
break;
|
||||
case vr::VREvent_ScrollDiscrete:
|
||||
{
|
||||
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;
|
||||
}
|
||||
case vr::VREvent_KeyboardDone: {
|
||||
vr::VROverlay()->GetKeyboardText(textBuf, sizeof textBuf);
|
||||
|
||||
int id = ImGui::GetActiveID();
|
||||
auto textInfo = ImGui::GetInputTextState(id);
|
||||
int bufSize = MultiByteToWideChar(CP_ACP, 0, textBuf, -1, NULL, 0);
|
||||
textInfo->TextW.resize(bufSize);
|
||||
MultiByteToWideChar(CP_ACP, 0, textBuf, -1, (LPWSTR)textInfo->TextW.Data, bufSize);
|
||||
textInfo->CurLenW = bufSize;
|
||||
textInfo->CurLenA = WideCharToMultiByte(CP_UTF8, 0, (LPCWCH)textInfo->TextW.Data, textInfo->TextW.Size, NULL, 0, NULL, NULL);
|
||||
|
||||
keyboardJustClosed = true;
|
||||
break;
|
||||
}
|
||||
case vr::VREvent_Quit:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
#endif
|
||||
fboTextureHandle;
|
||||
|
||||
vr::HmdVector2_t mouseScale = { (float) fboTextureWidth, (float) fboTextureHeight };
|
||||
|
||||
vr::VROverlay()->SetOverlayTexture(overlayMainHandle, &vrTex);
|
||||
vr::VROverlay()->SetOverlayMouseScale(overlayMainHandle, &mouseScale);
|
||||
}
|
||||
|
||||
const double dashboardInterval = 1.0 / 90.0; // fps
|
||||
double waitEventsTimeout = CalCtx.wantedUpdateInterval;
|
||||
|
||||
if (dashboardVisible && waitEventsTimeout > dashboardInterval)
|
||||
waitEventsTimeout = dashboardInterval;
|
||||
|
||||
if (immediateRedraw) {
|
||||
waitEventsTimeout = 0;
|
||||
immediateRedraw = false;
|
||||
}
|
||||
|
||||
glfwWaitEventsTimeout(waitEventsTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
|
||||
{
|
||||
_getcwd(cwd, MAX_PATH);
|
||||
HandleCommandLine(lpCmdLine);
|
||||
|
||||
#ifdef DEBUG_LOGS
|
||||
CreateConsole();
|
||||
#endif
|
||||
|
||||
if (!glfwInit())
|
||||
{
|
||||
MessageBox(nullptr, L"Failed to initialize GLFW", L"", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
glfwSetErrorCallback(GLFWErrorCallback);
|
||||
|
||||
try {
|
||||
InitVR();
|
||||
CreateGLFWWindow();
|
||||
InitCalibrator();
|
||||
LoadProfile(CalCtx);
|
||||
RunLoop();
|
||||
|
||||
vr::VR_Shutdown();
|
||||
|
||||
if (fboHandle)
|
||||
glDeleteFramebuffers(1, &fboHandle);
|
||||
|
||||
if (fboTextureHandle)
|
||||
glDeleteTextures(1, &fboTextureHandle);
|
||||
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImPlot::DestroyContext();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
catch (std::runtime_error &e)
|
||||
{
|
||||
std::cerr << "Runtime error: " << e.what() << std::endl;
|
||||
wchar_t message[1024];
|
||||
swprintf(message, 1024, L"%hs", e.what());
|
||||
MessageBox(nullptr, message, L"Runtime Error", 0);
|
||||
}
|
||||
|
||||
if (glfwWindow)
|
||||
glfwDestroyWindow(glfwWindow);
|
||||
|
||||
glfwTerminate();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void HandleCommandLine(LPWSTR lpCmdLine)
|
||||
{
|
||||
if (lstrcmp(lpCmdLine, L"-openvrpath") == 0)
|
||||
{
|
||||
auto vrErr = vr::VRInitError_None;
|
||||
vr::VR_Init(&vrErr, vr::VRApplication_Utility);
|
||||
if (vrErr == vr::VRInitError_None)
|
||||
{
|
||||
char cruntimePath[MAX_PATH] = { 0 };
|
||||
unsigned int pathLen;
|
||||
vr::VR_GetRuntimePath(cruntimePath, MAX_PATH, &pathLen);
|
||||
|
||||
printf("%s", cruntimePath);
|
||||
vr::VR_Shutdown();
|
||||
exit(0);
|
||||
}
|
||||
fprintf(stderr, "Failed to initialize OpenVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(vrErr));
|
||||
vr::VR_Shutdown();
|
||||
exit(-2);
|
||||
}
|
||||
else if (lstrcmp(lpCmdLine, L"-installmanifest") == 0)
|
||||
{
|
||||
auto vrErr = vr::VRInitError_None;
|
||||
vr::VR_Init(&vrErr, vr::VRApplication_Utility);
|
||||
if (vrErr == vr::VRInitError_None)
|
||||
{
|
||||
if (vr::VRApplications()->IsApplicationInstalled(OPENVR_APPLICATION_KEY))
|
||||
{
|
||||
char oldWd[MAX_PATH] = { 0 };
|
||||
auto vrAppErr = vr::VRApplicationError_None;
|
||||
vr::VRApplications()->GetApplicationPropertyString(OPENVR_APPLICATION_KEY, vr::VRApplicationProperty_WorkingDirectory_String, oldWd, MAX_PATH, &vrAppErr);
|
||||
if (vrAppErr != vr::VRApplicationError_None)
|
||||
{
|
||||
fprintf(stderr, "Failed to get old working dir, skipping removal: %s\n", vr::VRApplications()->GetApplicationsErrorNameFromEnum(vrAppErr));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string manifestPath = oldWd;
|
||||
manifestPath += "\\manifest.vrmanifest";
|
||||
std::cout << "Removing old manifest path: " << manifestPath << std::endl;
|
||||
vr::VRApplications()->RemoveApplicationManifest(manifestPath.c_str());
|
||||
}
|
||||
}
|
||||
std::string manifestPath = cwd;
|
||||
manifestPath += "\\manifest.vrmanifest";
|
||||
std::cout << "Adding manifest path: " << manifestPath << std::endl;
|
||||
auto vrAppErr = vr::VRApplications()->AddApplicationManifest(manifestPath.c_str());
|
||||
if (vrAppErr != vr::VRApplicationError_None)
|
||||
{
|
||||
fprintf(stderr, "Failed to add manifest: %s\n", vr::VRApplications()->GetApplicationsErrorNameFromEnum(vrAppErr));
|
||||
}
|
||||
else
|
||||
{
|
||||
vr::VRApplications()->SetApplicationAutoLaunch(OPENVR_APPLICATION_KEY, true);
|
||||
}
|
||||
vr::VR_Shutdown();
|
||||
exit(-2);
|
||||
}
|
||||
fprintf(stderr, "Failed to initialize OpenVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(vrErr));
|
||||
vr::VR_Shutdown();
|
||||
exit(-2);
|
||||
}
|
||||
else if (lstrcmp(lpCmdLine, L"-removemanifest") == 0)
|
||||
{
|
||||
auto vrErr = vr::VRInitError_None;
|
||||
vr::VR_Init(&vrErr, vr::VRApplication_Utility);
|
||||
if (vrErr == vr::VRInitError_None)
|
||||
{
|
||||
if (vr::VRApplications()->IsApplicationInstalled(OPENVR_APPLICATION_KEY))
|
||||
{
|
||||
std::string manifestPath = cwd;
|
||||
manifestPath += "\\manifest.vrmanifest";
|
||||
std::cout << "Removing manifest path: " << manifestPath << std::endl;
|
||||
vr::VRApplications()->RemoveApplicationManifest(manifestPath.c_str());
|
||||
}
|
||||
vr::VR_Shutdown();
|
||||
exit(0);
|
||||
}
|
||||
fprintf(stderr, "Failed to initialize OpenVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(vrErr));
|
||||
vr::VR_Shutdown();
|
||||
exit(-2);
|
||||
}
|
||||
else if (lstrcmp(lpCmdLine, L"-activatemultipledrivers") == 0)
|
||||
{
|
||||
int ret = -2;
|
||||
auto vrErr = vr::VRInitError_None;
|
||||
vr::VR_Init(&vrErr, vr::VRApplication_Utility);
|
||||
if (vrErr == vr::VRInitError_None)
|
||||
{
|
||||
try
|
||||
{
|
||||
ActivateMultipleDrivers();
|
||||
ret = 0;
|
||||
}
|
||||
catch (std::runtime_error &e)
|
||||
{
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Failed to initialize OpenVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(vrErr));
|
||||
}
|
||||
vr::VR_Shutdown();
|
||||
exit(ret);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{63BAE169-D595-4464-8E7E-50D6FD379348}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>OpenVRSpaceCalibrator</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>..\lib\imgui;..\lib\gl3w\include;..\lib\glfw\include;..\lib\openvr;..\lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>..\lib\glfw\lib-vc2015;..\lib\openvr\lib\win64</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>opengl32.lib;glfw3.lib;openvr_api.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>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>..\lib\imgui;..\lib\gl3w\include;..\lib\glfw\include;..\lib\openvr;..\lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>..\lib\glfw\lib-vc2015;..\lib\openvr\lib\win64</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>opengl32.lib;glfw3.lib;openvr_api.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>
|
||||
</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="ContinuousCalibration.h" />
|
||||
<ClInclude Include="EmbeddedFiles.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">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<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>
|
||||
<ClCompile Include="..\lib\imgui\imgui_impl_glfw.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\imgui\imgui_impl_opengl3.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</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="IPCClient.cpp" />
|
||||
<ClCompile Include="OpenVR-SpaceCalibrator.cpp" />
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="UserInterface.cpp" />
|
||||
<ClCompile Include="VRState.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="OpenVR-SpaceCalibrator.ico" />
|
||||
<Image Include="small.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="OpenVR-SpaceCalibrator.rc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\ImGui">
|
||||
<UniqueIdentifier>{948e4c13-6f2b-44e3-a8b7-1a12c79aeeb7}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<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">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="targetver.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Calibration.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="EmbeddedFiles.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="UserInterface.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Configuration.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IPCClient.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Version.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<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="ContinuousCalibration.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="VRState.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CalibrationMetrics.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Calibration.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\imgui\imgui.cpp">
|
||||
<Filter>Source Files\ImGui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\imgui\imgui_impl_glfw.cpp">
|
||||
<Filter>Source Files\ImGui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\imgui\imgui_impl_opengl3.cpp">
|
||||
<Filter>Source Files\ImGui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\gl3w\src\gl3w.c">
|
||||
<Filter>Source Files\GL</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\imgui\imgui_draw.cpp">
|
||||
<Filter>Source Files\ImGui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="EmbeddedFiles.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="UserInterface.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Configuration.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="IPCClient.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="OpenVR-SpaceCalibrator.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CalibrationCalc.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\imgui\imgui_tables.cpp">
|
||||
<Filter>Source Files\ImGui</Filter>
|
||||
</ClCompile>
|
||||
<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>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="small.ico">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
<Image Include="OpenVR-SpaceCalibrator.ico">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="OpenVR-SpaceCalibrator.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,766 @@
|
||||
#include "stdafx.h"
|
||||
#include "UserInterface.h"
|
||||
#include "Calibration.h"
|
||||
#include "Configuration.h"
|
||||
#include "VRState.h"
|
||||
#include "CalibrationMetrics.h"
|
||||
#include "../Version.h"
|
||||
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
void TextWithWidth(const char *label, const char *text, float width);
|
||||
|
||||
VRState LoadVRState();
|
||||
void BuildSystemSelection(const VRState &state);
|
||||
void BuildDeviceSelections(const VRState &state);
|
||||
void BuildProfileEditor();
|
||||
void BuildMenu(bool runningInOverlay);
|
||||
|
||||
static const ImGuiWindowFlags bareWindowFlags =
|
||||
ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoScrollWithMouse |
|
||||
ImGuiWindowFlags_NoCollapse;
|
||||
|
||||
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("OpenVRSpaceCalibrator", nullptr, bareWindowFlags))
|
||||
{
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImGui::GetStyleColorVec4(ImGuiCol_Button));
|
||||
|
||||
if (continuousCalibration) {
|
||||
BuildContinuousCalDisplay();
|
||||
}
|
||||
else {
|
||||
auto state = LoadVRState();
|
||||
|
||||
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 " - by tach/pushrax/bd_");
|
||||
if (runningInOverlay)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("- close VR overlay to use mouse");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void CCal_BasicInfo();
|
||||
void CCal_AlignParams();
|
||||
|
||||
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("Alignment speeds")) {
|
||||
CCal_AlignParams();
|
||||
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);
|
||||
|
||||
ImGui::SliderFloat(label, &v, (float)min, (float)max, "%1.2f", flags);
|
||||
|
||||
f = v / scale;
|
||||
}
|
||||
|
||||
void CCal_AlignParams() {
|
||||
if (ImGui::Button("Reset settings")) {
|
||||
CalCtx.ResetConfig();
|
||||
}
|
||||
|
||||
ImGui::SliderFloat("Recalibration threshold", &CalCtx.continuousCalibrationThreshold, 1.01f, 10.0f, "%1.1f", 0);
|
||||
if (ImGui::IsItemHovered(0)) {
|
||||
ImGui::SetTooltip("Controls how confident SpaceCalibrator must be in the new calibration before updating the calibration.\n"
|
||||
"Higher values calibrate less frequently, and may be useful on systems with lots of tracker drift."
|
||||
);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextWrapped(
|
||||
"Calibration speeds: 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)."
|
||||
);
|
||||
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::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);
|
||||
}
|
||||
|
||||
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 target device from application", &CalCtx.quashTargetInContinuous);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Enable static recalibration", &CalCtx.enableStaticRecalibration);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Enable debug logs", &Metrics::enableLogs);
|
||||
|
||||
// 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::None)
|
||||
{
|
||||
if (CalCtx.validProfile && !CalCtx.enabled)
|
||||
{
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, 1), "Reference (%s) HMD not detected, profile disabled", CalCtx.referenceTrackingSystem.c_str());
|
||||
ImGui::Text("");
|
||||
}
|
||||
|
||||
float width = ImGui::GetWindowContentRegionWidth(), scale = 1.0f;
|
||||
if (CalCtx.validProfile)
|
||||
{
|
||||
width -= style.FramePadding.x * 4.0f;
|
||||
scale = 1.0f / 4.0f;
|
||||
}
|
||||
|
||||
if (ImGui::Button("Start Calibration", ImVec2(width * scale, ImGui::GetTextLineHeight() * 2)))
|
||||
{
|
||||
ImGui::OpenPopup("Calibration Progress");
|
||||
StartCalibration();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Continuous Calibration", ImVec2(width * scale, ImGui::GetTextLineHeight() * 2))) {
|
||||
StartContinuousCalibration();
|
||||
}
|
||||
|
||||
if (CalCtx.validProfile)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Edit Calibration", ImVec2(width * scale, ImGui::GetTextLineHeight() * 2)))
|
||||
{
|
||||
CalCtx.state = CalibrationState::Editing;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear Calibration", ImVec2(width * scale, ImGui::GetTextLineHeight() * 2)))
|
||||
{
|
||||
CalCtx.Clear();
|
||||
SaveProfile(CalCtx);
|
||||
}
|
||||
}
|
||||
|
||||
width = ImGui::GetWindowContentRegionWidth();
|
||||
scale = 1.0f;
|
||||
if (CalCtx.chaperone.valid)
|
||||
{
|
||||
width -= style.FramePadding.x * 2.0f;
|
||||
scale = 0.5;
|
||||
}
|
||||
|
||||
ImGui::Text("");
|
||||
if (ImGui::Button("Copy Chaperone Bounds to profile", ImVec2(width * scale, ImGui::GetTextLineHeight() * 2)))
|
||||
{
|
||||
LoadChaperoneBounds();
|
||||
SaveProfile(CalCtx);
|
||||
}
|
||||
|
||||
if (CalCtx.chaperone.valid)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Paste Chaperone Bounds", ImVec2(width * scale, ImGui::GetTextLineHeight() * 2)))
|
||||
{
|
||||
ApplyChaperoneBounds();
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox(" Paste Chaperone Bounds automatically when geometry resets", &CalCtx.chaperone.autoApply))
|
||||
{
|
||||
SaveProfile(CalCtx);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text("");
|
||||
auto speed = CalCtx.calibrationSpeed;
|
||||
|
||||
ImGui::Columns(4, NULL, false);
|
||||
ImGui::Text("Calibration Speed");
|
||||
|
||||
ImGui::NextColumn();
|
||||
if (ImGui::RadioButton(" Fast ", speed == CalibrationContext::FAST))
|
||||
CalCtx.calibrationSpeed = CalibrationContext::FAST;
|
||||
|
||||
ImGui::NextColumn();
|
||||
if (ImGui::RadioButton(" Slow ", speed == CalibrationContext::SLOW))
|
||||
CalCtx.calibrationSpeed = CalibrationContext::SLOW;
|
||||
|
||||
ImGui::NextColumn();
|
||||
if (ImGui::RadioButton(" Very Slow ", speed == CalibrationContext::VERY_SLOW))
|
||||
CalCtx.calibrationSpeed = CalibrationContext::VERY_SLOW;
|
||||
|
||||
ImGui::Columns(1);
|
||||
}
|
||||
else if (CalCtx.state == CalibrationState::Editing)
|
||||
{
|
||||
BuildProfileEditor();
|
||||
|
||||
if (ImGui::Button("Save Profile", ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetTextLineHeight() * 2)))
|
||||
{
|
||||
SaveProfile(CalCtx);
|
||||
CalCtx.state = CalibrationState::None;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::Button("Calibration in progress...", ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetTextLineHeight() * 2));
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImVec4(0, 0, 0, 1));
|
||||
for (auto &message : CalCtx.messages)
|
||||
{
|
||||
switch (message.type)
|
||||
{
|
||||
case CalibrationContext::Message::String:
|
||||
ImGui::TextWrapped(message.str.c_str());
|
||||
break;
|
||||
case CalibrationContext::Message::Progress:
|
||||
float fraction = (float)message.progress / (float)message.target;
|
||||
ImGui::Text("");
|
||||
ImGui::ProgressBar(fraction, ImVec2(-1.0f, 0.0f), "");
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetFontSize() - style.FramePadding.y * 2);
|
||||
ImGui::Text(" %d%%", (int)(fraction * 100));
|
||||
break;
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (CalCtx.state == CalibrationState::None)
|
||||
{
|
||||
ImGui::Text("");
|
||||
if (ImGui::Button("Close", ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetTextLineHeight() * 2)))
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void BuildSystemSelection(const VRState &state)
|
||||
{
|
||||
if (state.trackingSystems.empty())
|
||||
{
|
||||
ImGui::Text("No tracked devices are present");
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
float paneWidth = ImGui::GetWindowContentRegionWidth() / 2 - style.FramePadding.x;
|
||||
|
||||
TextWithWidth("ReferenceSystemLabel", "Reference Space", paneWidth);
|
||||
ImGui::SameLine();
|
||||
TextWithWidth("TargetSystemLabel", "Target Space", paneWidth);
|
||||
|
||||
int currentReferenceSystem = -1;
|
||||
int currentTargetSystem = -1;
|
||||
int firstReferenceSystemNotTargetSystem = -1;
|
||||
|
||||
std::vector<const char *> referenceSystems;
|
||||
for (auto &str : state.trackingSystems)
|
||||
{
|
||||
if (str == CalCtx.referenceTrackingSystem)
|
||||
{
|
||||
currentReferenceSystem = (int) referenceSystems.size();
|
||||
}
|
||||
else if (firstReferenceSystemNotTargetSystem == -1 && str != CalCtx.targetTrackingSystem)
|
||||
{
|
||||
firstReferenceSystemNotTargetSystem = (int) referenceSystems.size();
|
||||
}
|
||||
referenceSystems.push_back(str.c_str());
|
||||
}
|
||||
|
||||
if (currentReferenceSystem == -1 && CalCtx.referenceTrackingSystem == "")
|
||||
{
|
||||
if (CalCtx.state == CalibrationState::ContinuousStandby) {
|
||||
auto iter = std::find(state.trackingSystems.begin(), state.trackingSystems.end(), CalCtx.referenceStandby.trackingSystem);
|
||||
if (iter != state.trackingSystems.end()) {
|
||||
currentReferenceSystem = iter - state.trackingSystems.begin();
|
||||
}
|
||||
}
|
||||
else {
|
||||
currentReferenceSystem = firstReferenceSystemNotTargetSystem;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PushItemWidth(paneWidth);
|
||||
ImGui::Combo("##ReferenceTrackingSystem", ¤tReferenceSystem, &referenceSystems[0], (int) referenceSystems.size());
|
||||
|
||||
if (currentReferenceSystem != -1 && currentReferenceSystem < (int) referenceSystems.size())
|
||||
{
|
||||
CalCtx.referenceTrackingSystem = std::string(referenceSystems[currentReferenceSystem]);
|
||||
if (CalCtx.referenceTrackingSystem == CalCtx.targetTrackingSystem)
|
||||
CalCtx.targetTrackingSystem = "";
|
||||
}
|
||||
|
||||
if (CalCtx.targetTrackingSystem == "") {
|
||||
if (CalCtx.state == CalibrationState::ContinuousStandby) {
|
||||
auto iter = std::find(state.trackingSystems.begin(), state.trackingSystems.end(), CalCtx.targetStandby.trackingSystem);
|
||||
if (iter != state.trackingSystems.end()) {
|
||||
currentTargetSystem = iter - state.trackingSystems.begin();
|
||||
}
|
||||
}
|
||||
else {
|
||||
currentTargetSystem = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<const char *> targetSystems;
|
||||
for (auto &str : state.trackingSystems)
|
||||
{
|
||||
if (str != CalCtx.referenceTrackingSystem)
|
||||
{
|
||||
if (str != "" && str == CalCtx.targetTrackingSystem)
|
||||
currentTargetSystem = (int) targetSystems.size();
|
||||
targetSystems.push_back(str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::Combo("##TargetTrackingSystem", ¤tTargetSystem, &targetSystems[0], (int) targetSystems.size());
|
||||
|
||||
if (currentTargetSystem != -1 && currentTargetSystem < targetSystems.size())
|
||||
{
|
||||
CalCtx.targetTrackingSystem = std::string(targetSystems[currentTargetSystem]);
|
||||
}
|
||||
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
void AppendSeparated(std::string &buffer, const std::string &suffix)
|
||||
{
|
||||
if (!buffer.empty())
|
||||
buffer += " | ";
|
||||
buffer += suffix;
|
||||
}
|
||||
|
||||
std::string LabelString(const VRDevice &device)
|
||||
{
|
||||
std::string label;
|
||||
|
||||
/*if (device.controllerRole == vr::TrackedControllerRole_LeftHand)
|
||||
label = "Left Controller";
|
||||
else if (device.controllerRole == vr::TrackedControllerRole_RightHand)
|
||||
label = "Right Controller";
|
||||
else if (device.deviceClass == vr::TrackedDeviceClass_Controller)
|
||||
label = "Controller";
|
||||
else if (device.deviceClass == vr::TrackedDeviceClass_HMD)
|
||||
label = "HMD";
|
||||
else if (device.deviceClass == vr::TrackedDeviceClass_GenericTracker)
|
||||
label = "Tracker";*/
|
||||
|
||||
AppendSeparated(label, device.model);
|
||||
AppendSeparated(label, device.serial);
|
||||
return label;
|
||||
}
|
||||
|
||||
std::string LabelString(const StandbyDevice& device) {
|
||||
std::string label("< ");
|
||||
|
||||
label += device.model;
|
||||
AppendSeparated(label, device.serial);
|
||||
|
||||
label += " >";
|
||||
return label;
|
||||
}
|
||||
|
||||
void BuildDeviceSelection(const VRState &state, int &initialSelected, const std::string &system, StandbyDevice &standbyDevice)
|
||||
{
|
||||
int selected = initialSelected;
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1), "Devices from: %s", system.c_str());
|
||||
|
||||
if (selected != -1)
|
||||
{
|
||||
bool matched = false;
|
||||
for (auto &device : state.devices)
|
||||
{
|
||||
if (device.trackingSystem != system)
|
||||
continue;
|
||||
|
||||
if (selected == device.id)
|
||||
{
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched)
|
||||
{
|
||||
// Device is no longer present.
|
||||
selected = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool standby = CalCtx.state == CalibrationState::ContinuousStandby;
|
||||
|
||||
if (selected == -1 && !standby)
|
||||
{
|
||||
for (auto &device : state.devices)
|
||||
{
|
||||
if (device.trackingSystem != system)
|
||||
continue;
|
||||
|
||||
if (device.controllerRole == vr::TrackedControllerRole_LeftHand)
|
||||
{
|
||||
selected = device.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (selected == -1) {
|
||||
for (auto& device : state.devices)
|
||||
{
|
||||
if (device.trackingSystem != system)
|
||||
continue;
|
||||
|
||||
selected = device.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selected == -1 && standby) {
|
||||
bool present = false;
|
||||
for (auto& device : state.devices)
|
||||
{
|
||||
if (device.trackingSystem != system)
|
||||
continue;
|
||||
|
||||
if (standbyDevice.model != device.model) continue;
|
||||
if (standbyDevice.serial != device.serial) continue;
|
||||
|
||||
present = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!present) {
|
||||
auto label = LabelString(standbyDevice);
|
||||
ImGui::Selectable(label.c_str(), true);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &device : state.devices)
|
||||
{
|
||||
if (device.trackingSystem != system)
|
||||
continue;
|
||||
|
||||
auto label = LabelString(device);
|
||||
if (ImGui::Selectable(label.c_str(), selected == device.id)) {
|
||||
selected = device.id;
|
||||
}
|
||||
}
|
||||
if (selected != initialSelected) {
|
||||
const auto& device = std::find_if(state.devices.begin(), state.devices.end(), [&](const auto& d) { return d.id == selected; });
|
||||
if (device == state.devices.end()) return;
|
||||
|
||||
initialSelected = selected;
|
||||
standbyDevice.trackingSystem = system;
|
||||
standbyDevice.model = device->model;
|
||||
standbyDevice.serial = device->serial;
|
||||
}
|
||||
}
|
||||
|
||||
void BuildDeviceSelections(const VRState &state)
|
||||
{
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
ImVec2 paneSize(ImGui::GetWindowContentRegionWidth() / 2 - style.FramePadding.x, ImGui::GetTextLineHeightWithSpacing() * 5 + style.ItemSpacing.y * 4);
|
||||
|
||||
ImGui::BeginChild("left device pane", paneSize, true);
|
||||
BuildDeviceSelection(state, CalCtx.referenceID, CalCtx.referenceTrackingSystem, CalCtx.referenceStandby);
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginChild("right device pane", paneSize, true);
|
||||
BuildDeviceSelection(state, CalCtx.targetID, CalCtx.targetTrackingSystem, CalCtx.targetStandby);
|
||||
ImGui::EndChild();
|
||||
|
||||
if (ImGui::Button("Identify selected devices (blinks LED or vibrates)", ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetTextLineHeightWithSpacing() + 4.0f)))
|
||||
{
|
||||
for (unsigned i = 0; i < 100; ++i)
|
||||
{
|
||||
vr::VRSystem()->TriggerHapticPulse(CalCtx.targetID, 0, 2000);
|
||||
vr::VRSystem()->TriggerHapticPulse(CalCtx.referenceID, 0, 2000);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VRState LoadVRState() {
|
||||
VRState state = VRState::Load();
|
||||
auto& trackingSystems = state.trackingSystems;
|
||||
|
||||
// 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);
|
||||
if (existing == trackingSystems.end()) {
|
||||
trackingSystems.push_back(CalCtx.referenceTrackingSystem);
|
||||
}
|
||||
|
||||
existing = std::find(trackingSystems.begin(), trackingSystems.end(), CalCtx.targetTrackingSystem);
|
||||
if (existing == trackingSystems.end()) {
|
||||
trackingSystems.push_back(CalCtx.targetTrackingSystem);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void BuildProfileEditor()
|
||||
{
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
float width = ImGui::GetWindowContentRegionWidth() / 3.0f - style.FramePadding.x;
|
||||
float widthF = width - style.FramePadding.x;
|
||||
|
||||
TextWithWidth("YawLabel", "Yaw", width);
|
||||
ImGui::SameLine();
|
||||
TextWithWidth("PitchLabel", "Pitch", width);
|
||||
ImGui::SameLine();
|
||||
TextWithWidth("RollLabel", "Roll", width);
|
||||
|
||||
ImGui::PushItemWidth(widthF);
|
||||
ImGui::InputDouble("##Yaw", &CalCtx.calibratedRotation(1), 0.1, 1.0, "%.8f");
|
||||
ImGui::SameLine();
|
||||
ImGui::InputDouble("##Pitch", &CalCtx.calibratedRotation(2), 0.1, 1.0, "%.8f");
|
||||
ImGui::SameLine();
|
||||
ImGui::InputDouble("##Roll", &CalCtx.calibratedRotation(0), 0.1, 1.0, "%.8f");
|
||||
|
||||
TextWithWidth("XLabel", "X", width);
|
||||
ImGui::SameLine();
|
||||
TextWithWidth("YLabel", "Y", width);
|
||||
ImGui::SameLine();
|
||||
TextWithWidth("ZLabel", "Z", width);
|
||||
|
||||
ImGui::InputDouble("##X", &CalCtx.calibratedTranslation(0), 1.0, 10.0, "%.8f");
|
||||
ImGui::SameLine();
|
||||
ImGui::InputDouble("##Y", &CalCtx.calibratedTranslation(1), 1.0, 10.0, "%.8f");
|
||||
ImGui::SameLine();
|
||||
ImGui::InputDouble("##Z", &CalCtx.calibratedTranslation(2), 1.0, 10.0, "%.8f");
|
||||
|
||||
TextWithWidth("ScaleLabel", "Scale", width);
|
||||
|
||||
ImGui::InputDouble("##Scale", &CalCtx.calibratedScale, 0.0001, 0.01, "%.8f");
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
void TextWithWidth(const char *label, const char *text, float width)
|
||||
{
|
||||
ImGui::BeginChild(label, ImVec2(width, ImGui::GetTextLineHeightWithSpacing()));
|
||||
ImGui::Text(text);
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void BuildMainWindow(bool runningInOverlay);
|
||||
void RequestImmediateRedraw();
|
||||
@@ -0,0 +1,72 @@
|
||||
#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);
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
};
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"source" : "builtin",
|
||||
"applications": [{
|
||||
"app_key": "steam.overlay.3368750",
|
||||
"app_key": "pushrax.SpaceCalibrator",
|
||||
"launch_type": "binary",
|
||||
"binary_path_windows": "SpaceCalibrator.exe",
|
||||
"binary_path_windows": "OpenVR-SpaceCalibrator.exe",
|
||||
"is_dashboard_overlay": true,
|
||||
|
||||
"strings": {
|
||||
"en_us": {
|
||||
"name": "Space Calibrator",
|
||||
"description": "Space Calibrator Overlay"
|
||||
"description": "OpenVR Space Calibrator Overlay"
|
||||
}
|
||||
}
|
||||
}]
|
||||
@@ -0,0 +1,6 @@
|
||||
// stdafx.cpp : source file that includes just the standard includes
|
||||
// OpenVR-SpaceCalibrator.pch will be the pre-compiled header
|
||||
// stdafx.obj will contain the pre-compiled type information
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#define EIGEN_MPL2_ONLY
|
||||
|
||||
#include "targetver.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <malloc.h>
|
||||
#include <memory.h>
|
||||
#include <tchar.h>
|
||||
#include <iostream>
|
||||
@@ -2,6 +2,5 @@
|
||||
"alwaysActivate": true,
|
||||
"name" : "01spacecalibrator",
|
||||
"directory" : "",
|
||||
"resourceOnly" : false,
|
||||
"activateOtherDriversWhenEnabled" : true
|
||||
"resourceOnly" : false
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"driver_01spacecalibrator" : {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "Hooking.h"
|
||||
|
||||
std::map<std::string, IHook *> IHook::hooks;
|
||||
|
||||
bool IHook::Exists(const std::string &name)
|
||||
{
|
||||
return hooks.find(name) != hooks.end();
|
||||
}
|
||||
|
||||
void IHook::Register(IHook *hook)
|
||||
{
|
||||
hooks[hook->name] = hook;
|
||||
}
|
||||
|
||||
void IHook::Unregister(IHook *hook)
|
||||
{
|
||||
hooks.erase(hook->name);
|
||||
}
|
||||
|
||||
void IHook::DestroyAll()
|
||||
{
|
||||
for (auto &hook : hooks)
|
||||
{
|
||||
hook.second->Destroy();
|
||||
}
|
||||
hooks.clear();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "Logging.h"
|
||||
#include <MinHook.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class IHook
|
||||
{
|
||||
public:
|
||||
const std::string name;
|
||||
|
||||
IHook(const std::string &name) : name(name) { }
|
||||
virtual ~IHook() { }
|
||||
|
||||
virtual void Destroy() = 0;
|
||||
|
||||
static bool Exists(const std::string &name);
|
||||
static void Register(IHook *hook);
|
||||
static void Unregister(IHook *hook);
|
||||
static void DestroyAll();
|
||||
|
||||
private:
|
||||
static std::map<std::string, IHook *> hooks;
|
||||
};
|
||||
|
||||
template<class FuncType> class Hook : public IHook
|
||||
{
|
||||
public:
|
||||
FuncType originalFunc = nullptr;
|
||||
Hook(const std::string &name) : IHook(name) { }
|
||||
|
||||
bool CreateHookInObjectVTable(void *object, int vtableOffset, void *detourFunction)
|
||||
{
|
||||
// For virtual objects, VC++ adds a pointer to the vtable as the first member.
|
||||
// To access the vtable, we simply dereference the object.
|
||||
void **vtable = *((void ***)object);
|
||||
|
||||
// The vtable itself is an array of pointers to member functions,
|
||||
// in the order they were declared in.
|
||||
targetFunc = vtable[vtableOffset];
|
||||
|
||||
auto err = MH_CreateHook(targetFunc, detourFunction, (LPVOID *)&originalFunc);
|
||||
if (err != MH_OK)
|
||||
{
|
||||
LOG("Failed to create hook for %s, error: %s", name.c_str(), MH_StatusToString(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = MH_EnableHook(targetFunc);
|
||||
if (err != MH_OK)
|
||||
{
|
||||
LOG("Failed to enable hook for %s, error: %s", name.c_str(), MH_StatusToString(err));
|
||||
MH_RemoveHook(targetFunc);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG("Enabled hook for %s", name.c_str());
|
||||
enabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Destroy()
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
MH_RemoveHook(targetFunc);
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool enabled = false;
|
||||
void* targetFunc = nullptr;
|
||||
};
|
||||
@@ -0,0 +1,230 @@
|
||||
#include "IPCServer.h"
|
||||
#include "Logging.h"
|
||||
#include "ServerTrackedDeviceProvider.h"
|
||||
|
||||
void IPCServer::HandleRequest(const protocol::Request &request, protocol::Response &response)
|
||||
{
|
||||
switch (request.type)
|
||||
{
|
||||
case protocol::RequestHandshake:
|
||||
response.type = protocol::ResponseHandshake;
|
||||
response.protocol.version = protocol::Version;
|
||||
break;
|
||||
|
||||
case protocol::RequestSetDeviceTransform:
|
||||
driver->SetDeviceTransform(request.setDeviceTransform);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
IPCServer::~IPCServer()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void IPCServer::Run()
|
||||
{
|
||||
mainThread = std::thread(RunThread, this);
|
||||
}
|
||||
|
||||
void IPCServer::Stop()
|
||||
{
|
||||
TRACE("IPCServer::Stop()");
|
||||
if (!running)
|
||||
return;
|
||||
|
||||
stop = true;
|
||||
SetEvent(connectEvent);
|
||||
mainThread.join();
|
||||
running = false;
|
||||
TRACE("IPCServer::Stop() finished");
|
||||
}
|
||||
|
||||
IPCServer::PipeInstance *IPCServer::CreatePipeInstance(HANDLE pipe)
|
||||
{
|
||||
auto pipeInst = new PipeInstance;
|
||||
pipeInst->pipe = pipe;
|
||||
pipeInst->server = this;
|
||||
pipes.insert(pipeInst);
|
||||
return pipeInst;
|
||||
}
|
||||
|
||||
void IPCServer::ClosePipeInstance(PipeInstance *pipeInst)
|
||||
{
|
||||
DisconnectNamedPipe(pipeInst->pipe);
|
||||
CloseHandle(pipeInst->pipe);
|
||||
pipes.erase(pipeInst);
|
||||
delete pipeInst;
|
||||
}
|
||||
|
||||
void IPCServer::RunThread(IPCServer *_this)
|
||||
{
|
||||
_this->running = true;
|
||||
LPTSTR pipeName = TEXT(OPENVR_SPACECALIBRATOR_PIPE_NAME);
|
||||
|
||||
HANDLE connectEvent = _this->connectEvent = CreateEvent(0, TRUE, TRUE, 0);
|
||||
if (!connectEvent)
|
||||
{
|
||||
LOG("CreateEvent failed in RunThread. Error: %d", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
OVERLAPPED connectOverlap;
|
||||
connectOverlap.hEvent = connectEvent;
|
||||
|
||||
HANDLE nextPipe;
|
||||
BOOL connectPending = CreateAndConnectInstance(&connectOverlap, nextPipe);
|
||||
|
||||
while (!_this->stop)
|
||||
{
|
||||
DWORD wait = WaitForSingleObjectEx(connectEvent, INFINITE, TRUE);
|
||||
|
||||
if (_this->stop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (wait == 0)
|
||||
{
|
||||
// When connectPending is false, the last call to CreateAndConnectInstance
|
||||
// picked up a connected client and triggered this event, so we can simply
|
||||
// create a new pipe instance for it. If true, the client was still pending
|
||||
// connection when CreateAndConnectInstance returned, so this event was triggered
|
||||
// internally and we need to flush out the result, or something like that.
|
||||
if (connectPending)
|
||||
{
|
||||
DWORD bytesConnect;
|
||||
BOOL success = GetOverlappedResult(nextPipe, &connectOverlap, &bytesConnect, FALSE);
|
||||
if (!success)
|
||||
{
|
||||
LOG("GetOverlappedResult failed in RunThread. Error: %d", GetLastError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LOG("IPC client connected");
|
||||
|
||||
auto pipeInst = _this->CreatePipeInstance(nextPipe);
|
||||
CompletedWriteCallback(0, sizeof protocol::Response, (LPOVERLAPPED) pipeInst);
|
||||
|
||||
connectPending = CreateAndConnectInstance(&connectOverlap, nextPipe);
|
||||
}
|
||||
else if (wait != WAIT_IO_COMPLETION)
|
||||
{
|
||||
printf("WaitForSingleObjectEx failed in RunThread. Error %d", GetLastError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &pipeInst : _this->pipes)
|
||||
{
|
||||
_this->ClosePipeInstance(pipeInst);
|
||||
}
|
||||
_this->pipes.clear();
|
||||
}
|
||||
|
||||
BOOL IPCServer::CreateAndConnectInstance(LPOVERLAPPED overlap, HANDLE &pipe)
|
||||
{
|
||||
pipe = CreateNamedPipe(
|
||||
TEXT(OPENVR_SPACECALIBRATOR_PIPE_NAME),
|
||||
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
|
||||
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
||||
PIPE_UNLIMITED_INSTANCES,
|
||||
sizeof protocol::Request,
|
||||
sizeof protocol::Response,
|
||||
1000,
|
||||
0
|
||||
);
|
||||
|
||||
if (pipe == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
LOG("CreateNamedPipe failed. Error: %d", GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ConnectNamedPipe(pipe, overlap);
|
||||
|
||||
switch(GetLastError())
|
||||
{
|
||||
case ERROR_IO_PENDING:
|
||||
// Mark a pending connection by returning true, and when the connection
|
||||
// completes an event will trigger automatically.
|
||||
return TRUE;
|
||||
|
||||
case ERROR_PIPE_CONNECTED:
|
||||
// Signal the event loop that a client is connected.
|
||||
if (SetEvent(overlap->hEvent))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
LOG("ConnectNamedPipe failed. Error: %d", GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void IPCServer::CompletedReadCallback(DWORD err, DWORD bytesRead, LPOVERLAPPED overlap)
|
||||
{
|
||||
PipeInstance *pipeInst = (PipeInstance *) overlap;
|
||||
BOOL success = FALSE;
|
||||
|
||||
if (err == 0 && bytesRead > 0)
|
||||
{
|
||||
pipeInst->server->HandleRequest(pipeInst->request, pipeInst->response);
|
||||
success = WriteFileEx(
|
||||
pipeInst->pipe,
|
||||
&pipeInst->response,
|
||||
sizeof protocol::Response,
|
||||
overlap,
|
||||
(LPOVERLAPPED_COMPLETION_ROUTINE) CompletedWriteCallback
|
||||
);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
if (err == ERROR_BROKEN_PIPE)
|
||||
{
|
||||
LOG("IPC client disconnecting normally");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG("IPC client disconnecting due to error (via CompletedReadCallback), error: %d, bytesRead: %d", err, bytesRead);
|
||||
}
|
||||
pipeInst->server->ClosePipeInstance(pipeInst);
|
||||
}
|
||||
}
|
||||
|
||||
void IPCServer::CompletedWriteCallback(DWORD err, DWORD bytesWritten, LPOVERLAPPED overlap)
|
||||
{
|
||||
PipeInstance *pipeInst = (PipeInstance *) overlap;
|
||||
BOOL success = FALSE;
|
||||
|
||||
if (err == 0 && bytesWritten == sizeof protocol::Response)
|
||||
{
|
||||
success = ReadFileEx(
|
||||
pipeInst->pipe,
|
||||
&pipeInst->request,
|
||||
sizeof protocol::Request,
|
||||
overlap,
|
||||
(LPOVERLAPPED_COMPLETION_ROUTINE) CompletedReadCallback
|
||||
);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
LOG("IPC client disconnecting due to error (via CompletedWriteCallback), error: %d, bytesWritten: %d", err, bytesWritten);
|
||||
pipeInst->server->ClosePipeInstance(pipeInst);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Protocol.h"
|
||||
|
||||
#include <thread>
|
||||
#include <set>
|
||||
#include <mutex>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
class ServerTrackedDeviceProvider;
|
||||
|
||||
class IPCServer
|
||||
{
|
||||
public:
|
||||
IPCServer(ServerTrackedDeviceProvider *driver) : driver(driver) { }
|
||||
~IPCServer();
|
||||
|
||||
void Run();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void HandleRequest(const protocol::Request &request, protocol::Response &response);
|
||||
|
||||
struct PipeInstance
|
||||
{
|
||||
OVERLAPPED overlap; // Used by the API
|
||||
HANDLE pipe;
|
||||
IPCServer *server;
|
||||
|
||||
protocol::Request request;
|
||||
protocol::Response response;
|
||||
};
|
||||
|
||||
PipeInstance *CreatePipeInstance(HANDLE pipe);
|
||||
void ClosePipeInstance(PipeInstance *pipeInst);
|
||||
|
||||
static void RunThread(IPCServer *_this);
|
||||
static BOOL CreateAndConnectInstance(LPOVERLAPPED overlap, HANDLE &pipe);
|
||||
static void WINAPI CompletedReadCallback(DWORD err, DWORD bytesRead, LPOVERLAPPED overlap);
|
||||
static void WINAPI CompletedWriteCallback(DWORD err, DWORD bytesWritten, LPOVERLAPPED overlap);
|
||||
|
||||
std::thread mainThread;
|
||||
|
||||
bool running = false;
|
||||
bool stop = false;
|
||||
|
||||
std::set<PipeInstance *> pipes;
|
||||
HANDLE connectEvent;
|
||||
|
||||
ServerTrackedDeviceProvider *driver;
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
#include "Logging.h"
|
||||
#include "Hooking.h"
|
||||
#include "InterfaceHookInjector.h"
|
||||
#include "ServerTrackedDeviceProvider.h"
|
||||
|
||||
static ServerTrackedDeviceProvider *Driver = nullptr;
|
||||
|
||||
static Hook<void*(*)(vr::IVRDriverContext *, const char *, vr::EVRInitError *)>
|
||||
GetGenericInterfaceHook("IVRDriverContext::GetGenericInterface");
|
||||
|
||||
static Hook<void(*)(vr::IVRServerDriverHost *, uint32_t, const vr::DriverPose_t &, uint32_t)>
|
||||
TrackedDevicePoseUpdatedHook005("IVRServerDriverHost005::TrackedDevicePoseUpdated");
|
||||
|
||||
static Hook<void(*)(vr::IVRServerDriverHost *, uint32_t, const vr::DriverPose_t &, uint32_t)>
|
||||
TrackedDevicePoseUpdatedHook006("IVRServerDriverHost006::TrackedDevicePoseUpdated");
|
||||
|
||||
static void DetourTrackedDevicePoseUpdated005(vr::IVRServerDriverHost *_this, uint32_t unWhichDevice, const vr::DriverPose_t &newPose, uint32_t unPoseStructSize)
|
||||
{
|
||||
//TRACE("ServerTrackedDeviceProvider::DetourTrackedDevicePoseUpdated(%d)", unWhichDevice);
|
||||
auto pose = newPose;
|
||||
if (Driver->HandleDevicePoseUpdated(unWhichDevice, pose))
|
||||
{
|
||||
TrackedDevicePoseUpdatedHook005.originalFunc(_this, unWhichDevice, pose, unPoseStructSize);
|
||||
}
|
||||
}
|
||||
|
||||
static void DetourTrackedDevicePoseUpdated006(vr::IVRServerDriverHost *_this, uint32_t unWhichDevice, const vr::DriverPose_t &newPose, uint32_t unPoseStructSize)
|
||||
{
|
||||
//TRACE("ServerTrackedDeviceProvider::DetourTrackedDevicePoseUpdated(%d)", unWhichDevice);
|
||||
auto pose = newPose;
|
||||
if (Driver->HandleDevicePoseUpdated(unWhichDevice, pose))
|
||||
{
|
||||
TrackedDevicePoseUpdatedHook006.originalFunc(_this, unWhichDevice, pose, unPoseStructSize);
|
||||
}
|
||||
}
|
||||
|
||||
static void *DetourGetGenericInterface(vr::IVRDriverContext *_this, const char *pchInterfaceVersion, vr::EVRInitError *peError)
|
||||
{
|
||||
TRACE("ServerTrackedDeviceProvider::DetourGetGenericInterface(%s)", pchInterfaceVersion);
|
||||
auto originalInterface = GetGenericInterfaceHook.originalFunc(_this, pchInterfaceVersion, peError);
|
||||
|
||||
std::string iface(pchInterfaceVersion);
|
||||
if (iface == "IVRServerDriverHost_005")
|
||||
{
|
||||
if (!IHook::Exists(TrackedDevicePoseUpdatedHook005.name))
|
||||
{
|
||||
TrackedDevicePoseUpdatedHook005.CreateHookInObjectVTable(originalInterface, 1, &DetourTrackedDevicePoseUpdated005);
|
||||
IHook::Register(&TrackedDevicePoseUpdatedHook005);
|
||||
}
|
||||
}
|
||||
else if (iface == "IVRServerDriverHost_006")
|
||||
{
|
||||
if (!IHook::Exists(TrackedDevicePoseUpdatedHook006.name))
|
||||
{
|
||||
TrackedDevicePoseUpdatedHook006.CreateHookInObjectVTable(originalInterface, 1, &DetourTrackedDevicePoseUpdated006);
|
||||
IHook::Register(&TrackedDevicePoseUpdatedHook006);
|
||||
}
|
||||
}
|
||||
|
||||
return originalInterface;
|
||||
}
|
||||
|
||||
void InjectHooks(ServerTrackedDeviceProvider *driver, vr::IVRDriverContext *pDriverContext)
|
||||
{
|
||||
Driver = driver;
|
||||
|
||||
auto err = MH_Initialize();
|
||||
if (err == MH_OK)
|
||||
{
|
||||
GetGenericInterfaceHook.CreateHookInObjectVTable(pDriverContext, 0, &DetourGetGenericInterface);
|
||||
IHook::Register(&GetGenericInterfaceHook);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG("MH_Initialize error: %s", MH_StatusToString(err));
|
||||
}
|
||||
}
|
||||
|
||||
void DisableHooks()
|
||||
{
|
||||
IHook::DestroyAll();
|
||||
MH_Uninitialize();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <openvr_driver.h>
|
||||
|
||||
class ServerTrackedDeviceProvider;
|
||||
|
||||
static void DetourTrackedDevicePoseUpdated(vr::IVRServerDriverHost * _this, uint32_t unWhichDevice, const vr::DriverPose_t & newPose, uint32_t unPoseStructSize);
|
||||
|
||||
void InjectHooks(ServerTrackedDeviceProvider *driver, vr::IVRDriverContext *pDriverContext);
|
||||
void DisableHooks();
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#define _CRT_SECURE_NO_DEPRECATE
|
||||
#include "Logging.h"
|
||||
#include <chrono>
|
||||
|
||||
FILE *LogFile;
|
||||
|
||||
void OpenLogFile()
|
||||
{
|
||||
LogFile = fopen("space_calibrator_driver.log", "a");
|
||||
if (LogFile == nullptr)
|
||||
{
|
||||
LogFile = stderr;
|
||||
}
|
||||
}
|
||||
|
||||
tm TimeForLog()
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto nowTime = std::chrono::system_clock::to_time_t(now);
|
||||
tm value;
|
||||
auto tm = localtime_s(&value, &nowTime);
|
||||
return value;
|
||||
}
|
||||
|
||||
void LogFlush()
|
||||
{
|
||||
fflush(LogFile);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
extern FILE *LogFile;
|
||||
|
||||
void OpenLogFile();
|
||||
tm TimeForLog();
|
||||
void LogFlush();
|
||||
|
||||
#ifndef LOG
|
||||
#define LOG(fmt, ...) do { \
|
||||
tm logNow = TimeForLog(); \
|
||||
fprintf(LogFile, "[%02d:%02d:%02d] " fmt "\n", logNow.tm_hour, logNow.tm_min, logNow.tm_sec, __VA_ARGS__); \
|
||||
LogFlush(); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#define TRACE(...) {}
|
||||
|
||||
#ifndef TRACE
|
||||
#define TRACE LOG
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "OpenVR-SpaceCalibratorDriver.h"
|
||||
#include "ServerTrackedDeviceProvider.h"
|
||||
#include "VRWatchdogProvider.h"
|
||||
#include "Logging.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <openvr_driver.h>
|
||||
|
||||
OPENVRSPACECALIBRATORDRIVER_API void *HmdDriverFactory(const char *pInterfaceName, int *pReturnCode)
|
||||
{
|
||||
TRACE("HmdDriverFactory(%s)", pInterfaceName);
|
||||
|
||||
static ServerTrackedDeviceProvider server;
|
||||
static VRWatchdogProvider watchdog;
|
||||
|
||||
if (std::strcmp(vr::IServerTrackedDeviceProvider_Version, pInterfaceName) == 0)
|
||||
{
|
||||
return &server;
|
||||
}
|
||||
else if (std::strcmp(vr::IVRWatchdogProvider_Version, pInterfaceName) == 0)
|
||||
{
|
||||
return &watchdog;
|
||||
}
|
||||
|
||||
if (pReturnCode)
|
||||
{
|
||||
*pReturnCode = vr::VRInitError_Init_InterfaceNotFound;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef OPENVRSPACECALIBRATORDRIVER_EXPORTS
|
||||
#define OPENVRSPACECALIBRATORDRIVER_API extern "C" __declspec(dllexport)
|
||||
#else
|
||||
#define OPENVRSPACECALIBRATORDRIVER_API extern "C" __declspec(dllimport)
|
||||
#endif
|
||||
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{A61324AD-CE32-46D1-A95E-7E28A6D8CCA7}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>OpenVRSpaceCalibratorDriver</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<TargetName>driver_01spacecalibrator</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<TargetName>driver_01spacecalibrator</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;OPENVRSPACECALIBRATORDRIVER_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>..\lib;..\lib\openvr;..\lib\MinHook\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>..\lib\MinHook\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<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'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;OPENVRSPACECALIBRATORDRIVER_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>..\lib;..\lib\openvr;..\lib\MinHook\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>..\lib\MinHook\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<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>
|
||||
<ClInclude Include="..\Protocol.h" />
|
||||
<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" />
|
||||
<ClInclude Include="VRWatchdogProvider.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
</PrecompiledHeader>
|
||||
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Hooking.cpp" />
|
||||
<ClCompile Include="InterfaceHookInjector.cpp" />
|
||||
<ClCompile Include="IPCServer.cpp" />
|
||||
<ClCompile Include="Logging.cpp" />
|
||||
<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>
|
||||
</Project>
|
||||
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="OpenVR-SpaceCalibratorDriver.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Logging.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ServerTrackedDeviceProvider.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="VRWatchdogProvider.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IPCServer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Protocol.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Hooking.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<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">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Logging.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ServerTrackedDeviceProvider.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="IPCServer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Hooking.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="InterfaceHookInjector.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,256 @@
|
||||
#include "ServerTrackedDeviceProvider.h"
|
||||
#include "Logging.h"
|
||||
#include "InterfaceHookInjector.h"
|
||||
#include "IsometryTransform.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
vr::EVRInitError ServerTrackedDeviceProvider::Init(vr::IVRDriverContext *pDriverContext)
|
||||
{
|
||||
TRACE("ServerTrackedDeviceProvider::Init()");
|
||||
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;
|
||||
}
|
||||
|
||||
void ServerTrackedDeviceProvider::Cleanup()
|
||||
{
|
||||
TRACE("ServerTrackedDeviceProvider::Cleanup()");
|
||||
server.Stop();
|
||||
shmem.Close();
|
||||
DisableHooks();
|
||||
VR_CLEANUP_SERVER_DRIVER_CONTEXT();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
vr::HmdQuaternion_t convert(const Eigen::Quaterniond& q) {
|
||||
vr::HmdQuaternion_t result;
|
||||
result.w = q.w();
|
||||
result.x = q.x();
|
||||
result.y = q.y();
|
||||
result.z = q.z();
|
||||
return result;
|
||||
}
|
||||
|
||||
vr::HmdVector3_t convert(const Eigen::Vector3d& v) {
|
||||
vr::HmdVector3_t result;
|
||||
result.v[0] = (float) v.x();
|
||||
result.v[1] = (float) v.y();
|
||||
result.v[2] = (float) v.z();
|
||||
return result;
|
||||
}
|
||||
|
||||
Eigen::Quaterniond convert(const vr::HmdQuaternion_t& q) {
|
||||
return Eigen::Quaterniond(q.w, q.x, q.y, q.z);
|
||||
}
|
||||
|
||||
Eigen::Vector3d convert(const vr::HmdVector3d_t& v) {
|
||||
return Eigen::Vector3d(v.v[0], v.v[1], v.v[2]);
|
||||
}
|
||||
|
||||
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),
|
||||
(lhs.w * rhs.x) + (lhs.x * rhs.w) + (lhs.y * rhs.z) - (lhs.z * rhs.y),
|
||||
(lhs.w * rhs.y) + (lhs.y * rhs.w) + (lhs.z * rhs.x) - (lhs.x * rhs.z),
|
||||
(lhs.w * rhs.z) + (lhs.z * rhs.w) + (lhs.x * rhs.y) - (lhs.y * rhs.x)
|
||||
};
|
||||
}
|
||||
|
||||
inline vr::HmdVector3d_t quaternionRotateVector(const vr::HmdQuaternion_t& quat, const double(&vector)[3]) {
|
||||
vr::HmdQuaternion_t vectorQuat = { 0.0, vector[0], vector[1] , vector[2] };
|
||||
vr::HmdQuaternion_t conjugate = { quat.w, -quat.x, -quat.y, -quat.z };
|
||||
auto rotatedVectorQuat = quat * vectorQuat * conjugate;
|
||||
return { rotatedVectorQuat.x, rotatedVectorQuat.y, rotatedVectorQuat.z };
|
||||
}
|
||||
|
||||
void ServerTrackedDeviceProvider::SetDeviceTransform(const protocol::SetDeviceTransform& newTransform)
|
||||
{
|
||||
auto &tf = transforms[newTransform.openVRID];
|
||||
tf.enabled = newTransform.enabled;
|
||||
|
||||
if (newTransform.updateTranslation) {
|
||||
tf.targetTransform.translation = convert(newTransform.translation);
|
||||
if (!newTransform.lerp) {
|
||||
tf.transform.translation = tf.targetTransform.translation;
|
||||
}
|
||||
}
|
||||
|
||||
if (newTransform.updateRotation) {
|
||||
tf.targetTransform.rotation = convert(newTransform.rotation);
|
||||
|
||||
if (!newTransform.lerp) {
|
||||
tf.transform.rotation = tf.targetTransform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
if (newTransform.updateScale)
|
||||
tf.scale = newTransform.scale;
|
||||
|
||||
tf.quash = newTransform.quash;
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
if (tf.quash) {
|
||||
pose.vecPosition[0] = -pose.vecWorldFromDriverTranslation[0];
|
||||
pose.vecPosition[1] = -pose.vecWorldFromDriverTranslation[1] + 9001; // put it 9001m above the origin
|
||||
pose.vecPosition[2] = -pose.vecWorldFromDriverTranslation[2];
|
||||
} else if (tf.enabled)
|
||||
{
|
||||
pose.vecPosition[0] *= tf.scale;
|
||||
pose.vecPosition[1] *= tf.scale;
|
||||
pose.vecPosition[2] *= tf.scale;
|
||||
|
||||
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());
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
#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:
|
||||
////// Start vr::IServerTrackedDeviceProvider functions
|
||||
|
||||
/** initializes the driver. This will be called before any other methods are called. */
|
||||
virtual vr::EVRInitError Init(vr::IVRDriverContext *pDriverContext) override;
|
||||
|
||||
/** cleans up the driver right before it is unloaded */
|
||||
virtual void Cleanup() override;
|
||||
|
||||
/** Returns the version of the ITrackedDeviceServerDriver interface used by this driver */
|
||||
virtual const char * const *GetInterfaceVersions() { return vr::k_InterfaceVersions; }
|
||||
|
||||
/** Allows the driver do to some work in the main loop of the server. */
|
||||
virtual void RunFrame() { }
|
||||
|
||||
/** Returns true if the driver wants to block Standby mode. */
|
||||
virtual bool ShouldBlockStandbyMode() { return false; }
|
||||
|
||||
/** Called when the system is entering Standby mode. The driver should switch itself into whatever sort of low-power
|
||||
* state it has. */
|
||||
virtual void EnterStandby() { }
|
||||
|
||||
/** Called when the system is leaving Standby mode. The driver should switch itself back to
|
||||
full operation. */
|
||||
virtual void LeaveStandby() { }
|
||||
|
||||
////// End vr::IServerTrackedDeviceProvider functions
|
||||
|
||||
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;
|
||||
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;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <openvr_driver.h>
|
||||
|
||||
class VRWatchdogProvider : public vr::IVRWatchdogProvider
|
||||
{
|
||||
/** initializes the driver in watchdog mode. */
|
||||
virtual vr::EVRInitError Init(vr::IVRDriverContext *pDriverContext)
|
||||
{
|
||||
VR_INIT_WATCHDOG_DRIVER_CONTEXT(pDriverContext);
|
||||
return vr::VRInitError_None;
|
||||
}
|
||||
|
||||
/** cleans up the driver right before it is unloaded */
|
||||
virtual void Cleanup()
|
||||
{
|
||||
VR_CLEANUP_WATCHDOG_DRIVER_CONTEXT()
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "Logging.h"
|
||||
#include "../Version.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <cstdio>
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
OpenLogFile();
|
||||
LOG("OpenVR-SpaceCalibratorDriver " SPACECAL_VERSION_STRING " loaded");
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
LOG("OpenVR-SpaceCalibratorDriver unloaded");
|
||||
break;
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <stdexcept>
|
||||
#include <functional>
|
||||
|
||||
#ifndef _OPENVR_API
|
||||
#include <openvr_driver.h>
|
||||
#endif
|
||||
|
||||
#define OPENVR_SPACECALIBRATOR_PIPE_NAME "\\\\.\\pipe\\OpenVRSpaceCalibratorDriver"
|
||||
#define OPENVR_SPACECALIBRATOR_SHMEM_NAME "OpenVRSpaceCalibratorPoseMemoryV1"
|
||||
|
||||
#ifdef _OPENVR_API
|
||||
|
||||
namespace vr {
|
||||
// We can't include openvr_driver.h as it will result in multiple definition of some structures.
|
||||
// However, we need to share driver-specific structures with the client application, so duplicate them
|
||||
// here.
|
||||
|
||||
struct DriverPose_t
|
||||
{
|
||||
/* Time offset of this pose, in seconds from the actual time of the pose,
|
||||
* relative to the time of the PoseUpdated() call made by the driver.
|
||||
*/
|
||||
double poseTimeOffset;
|
||||
|
||||
/* Generally, the pose maintained by a driver
|
||||
* is in an inertial coordinate system different
|
||||
* from the world system of x+ right, y+ up, z+ back.
|
||||
* Also, the driver is not usually tracking the "head" position,
|
||||
* but instead an internal IMU or another reference point in the HMD.
|
||||
* The following two transforms transform positions and orientations
|
||||
* to app world space from driver world space,
|
||||
* and to HMD head space from driver local body space.
|
||||
*
|
||||
* We maintain the driver pose state in its internal coordinate system,
|
||||
* so we can do the pose prediction math without having to
|
||||
* use angular acceleration. A driver's angular acceleration is generally not measured,
|
||||
* and is instead calculated from successive samples of angular velocity.
|
||||
* This leads to a noisy angular acceleration values, which are also
|
||||
* lagged due to the filtering required to reduce noise to an acceptable level.
|
||||
*/
|
||||
vr::HmdQuaternion_t qWorldFromDriverRotation;
|
||||
double vecWorldFromDriverTranslation[3];
|
||||
|
||||
vr::HmdQuaternion_t qDriverFromHeadRotation;
|
||||
double vecDriverFromHeadTranslation[3];
|
||||
|
||||
/* State of driver pose, in meters and radians. */
|
||||
/* Position of the driver tracking reference in driver world space
|
||||
* +[0] (x) is right
|
||||
* +[1] (y) is up
|
||||
* -[2] (z) is forward
|
||||
*/
|
||||
double vecPosition[3];
|
||||
|
||||
/* Velocity of the pose in meters/second */
|
||||
double vecVelocity[3];
|
||||
|
||||
/* Acceleration of the pose in meters/second */
|
||||
double vecAcceleration[3];
|
||||
|
||||
/* Orientation of the tracker, represented as a quaternion */
|
||||
vr::HmdQuaternion_t qRotation;
|
||||
|
||||
/* Angular velocity of the pose in axis-angle
|
||||
* representation. The direction is the angle of
|
||||
* rotation and the magnitude is the angle around
|
||||
* that axis in radians/second. */
|
||||
double vecAngularVelocity[3];
|
||||
|
||||
/* Angular acceleration of the pose in axis-angle
|
||||
* representation. The direction is the angle of
|
||||
* rotation and the magnitude is the angle around
|
||||
* that axis in radians/second^2. */
|
||||
double vecAngularAcceleration[3];
|
||||
|
||||
ETrackingResult result;
|
||||
|
||||
bool poseIsValid;
|
||||
bool willDriftInYaw;
|
||||
bool shouldApplyHeadModel;
|
||||
bool deviceIsConnected;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace protocol
|
||||
{
|
||||
const uint32_t Version = 4;
|
||||
|
||||
enum RequestType
|
||||
{
|
||||
RequestInvalid,
|
||||
RequestHandshake,
|
||||
RequestSetDeviceTransform,
|
||||
RequestSetAlignmentSpeedParams,
|
||||
RequestDebugOffset
|
||||
};
|
||||
|
||||
enum ResponseType
|
||||
{
|
||||
ResponseInvalid,
|
||||
ResponseHandshake,
|
||||
ResponseSuccess,
|
||||
};
|
||||
|
||||
struct 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;
|
||||
bool enabled;
|
||||
bool updateTranslation;
|
||||
bool updateRotation;
|
||||
bool updateScale;
|
||||
vr::HmdVector3d_t translation;
|
||||
vr::HmdQuaternion_t rotation;
|
||||
double scale;
|
||||
bool lerp;
|
||||
bool quash;
|
||||
|
||||
SetDeviceTransform(uint32_t id, bool enabled) :
|
||||
openVRID(id), enabled(enabled), updateTranslation(false), updateRotation(false), updateScale(false), lerp(false), quash(false) { }
|
||||
|
||||
SetDeviceTransform(uint32_t id, bool enabled, vr::HmdVector3d_t translation) :
|
||||
openVRID(id), enabled(enabled), updateTranslation(true), updateRotation(false), updateScale(false), translation(translation), lerp(false), quash(false) { }
|
||||
|
||||
SetDeviceTransform(uint32_t id, bool enabled, vr::HmdQuaternion_t rotation) :
|
||||
openVRID(id), enabled(enabled), updateTranslation(false), updateRotation(true), updateScale(false), rotation(rotation), lerp(false), quash(false) { }
|
||||
|
||||
SetDeviceTransform(uint32_t id, bool enabled, double scale) :
|
||||
openVRID(id), enabled(enabled), updateTranslation(false), updateRotation(false), updateScale(true), scale(scale), lerp(false), quash(false) { }
|
||||
|
||||
SetDeviceTransform(uint32_t id, bool enabled, vr::HmdVector3d_t translation, vr::HmdQuaternion_t rotation) :
|
||||
openVRID(id), enabled(enabled), updateTranslation(true), updateRotation(true), updateScale(false), translation(translation), rotation(rotation), lerp(false), quash(false) { }
|
||||
|
||||
SetDeviceTransform(uint32_t id, bool enabled, vr::HmdVector3d_t translation, vr::HmdQuaternion_t rotation, double scale) :
|
||||
openVRID(id), enabled(enabled), updateTranslation(true), updateRotation(true), updateScale(true), translation(translation), rotation(rotation), scale(scale), lerp(false), quash(false) { }
|
||||
};
|
||||
|
||||
struct Request
|
||||
{
|
||||
RequestType type;
|
||||
|
||||
union {
|
||||
SetDeviceTransform setDeviceTransform;
|
||||
AlignmentSpeedParams setAlignmentSpeedParams;
|
||||
};
|
||||
|
||||
Request() : type(RequestInvalid) { }
|
||||
Request(RequestType type) : type(type) { }
|
||||
Request(AlignmentSpeedParams params) : type(RequestType::RequestSetAlignmentSpeedParams), setAlignmentSpeedParams(params) {}
|
||||
};
|
||||
|
||||
struct Response
|
||||
{
|
||||
ResponseType type;
|
||||
|
||||
union {
|
||||
Protocol protocol;
|
||||
};
|
||||
|
||||
Response() : type(ResponseInvalid) { }
|
||||
Response(ResponseType type) : type(type) { }
|
||||
};
|
||||
|
||||
class DriverPoseShmem {
|
||||
public:
|
||||
struct AugmentedPose {
|
||||
LARGE_INTEGER sample_time;
|
||||
int deviceId;
|
||||
vr::DriverPose_t pose;
|
||||
};
|
||||
private:
|
||||
static const uint32_t SYNC_ACTIVE_POSE_B = 0x80000000;
|
||||
static const uint32_t BUFFERED_SAMPLES = 64 * 1024;
|
||||
|
||||
struct ShmemData {
|
||||
std::atomic<uint64_t> index;
|
||||
AugmentedPose poses[BUFFERED_SAMPLES];
|
||||
};
|
||||
|
||||
private:
|
||||
HANDLE hMapFile;
|
||||
ShmemData* pData;
|
||||
uint64_t cursor;
|
||||
|
||||
AugmentedPose lastPose[vr::k_unMaxTrackedDeviceCount];
|
||||
|
||||
std::string LastErrorString(DWORD lastError)
|
||||
{
|
||||
LPSTR buffer = nullptr;
|
||||
size_t size = FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, lastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, NULL
|
||||
);
|
||||
|
||||
std::string message(buffer, size);
|
||||
LocalFree(buffer);
|
||||
return message;
|
||||
}
|
||||
|
||||
public:
|
||||
operator bool() const {
|
||||
return pData != nullptr;
|
||||
}
|
||||
|
||||
bool operator!() const {
|
||||
return pData == nullptr;
|
||||
}
|
||||
|
||||
DriverPoseShmem() {
|
||||
hMapFile = INVALID_HANDLE_VALUE;
|
||||
pData = nullptr;
|
||||
cursor = 0;
|
||||
}
|
||||
|
||||
~DriverPoseShmem() {
|
||||
Close();
|
||||
}
|
||||
|
||||
void Close() {
|
||||
if (pData) UnmapViewOfFile(pData);
|
||||
if (hMapFile) CloseHandle(hMapFile);
|
||||
}
|
||||
|
||||
bool Create(LPCSTR segment_name) {
|
||||
Close();
|
||||
|
||||
hMapFile = CreateFileMappingA(
|
||||
INVALID_HANDLE_VALUE,
|
||||
NULL,
|
||||
PAGE_READWRITE,
|
||||
0,
|
||||
sizeof(ShmemData),
|
||||
segment_name
|
||||
);
|
||||
|
||||
if (!hMapFile) return false;
|
||||
|
||||
pData = reinterpret_cast<ShmemData*>(MapViewOfFile(
|
||||
hMapFile,
|
||||
FILE_MAP_ALL_ACCESS,
|
||||
0,
|
||||
0,
|
||||
sizeof(ShmemData)
|
||||
));
|
||||
|
||||
return !!pData;
|
||||
}
|
||||
|
||||
|
||||
void Open(LPCSTR segment_name) {
|
||||
Close();
|
||||
|
||||
hMapFile = OpenFileMappingA(
|
||||
FILE_MAP_ALL_ACCESS,
|
||||
FALSE,
|
||||
segment_name
|
||||
);
|
||||
|
||||
if (!hMapFile) {
|
||||
throw std::runtime_error("Failed to open pose data shared memory segment: " + LastErrorString(GetLastError()));
|
||||
}
|
||||
|
||||
pData = reinterpret_cast<ShmemData*>(MapViewOfFile(
|
||||
hMapFile,
|
||||
FILE_MAP_ALL_ACCESS,
|
||||
0,
|
||||
0,
|
||||
sizeof(ShmemData)
|
||||
));
|
||||
|
||||
if (!pData) {
|
||||
throw std::runtime_error("Failed to map pose data shared memory segment: " + LastErrorString(GetLastError()));
|
||||
}
|
||||
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof tmp, "Opened shmem segment: %p\n", pData);
|
||||
OutputDebugStringA(tmp);
|
||||
}
|
||||
|
||||
void ReadNewPoses(std::function<void(AugmentedPose const&)> cb) {
|
||||
if (!pData) throw std::runtime_error("Not open");
|
||||
|
||||
uint64_t cur_index = pData->index.load(std::memory_order_acquire);
|
||||
if (cur_index < cursor || cur_index - cursor > BUFFERED_SAMPLES / 2) {
|
||||
if (cur_index < BUFFERED_SAMPLES / 2)
|
||||
cursor = cur_index;
|
||||
else
|
||||
cursor = cur_index - BUFFERED_SAMPLES / 2;
|
||||
}
|
||||
|
||||
while (cursor < cur_index) {
|
||||
cb(pData->poses[cursor % BUFFERED_SAMPLES]);
|
||||
cursor++;
|
||||
}
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
}
|
||||
|
||||
bool GetPose(int index, vr::DriverPose_t& pose, LARGE_INTEGER *pSampleTime = NULL) {
|
||||
ReadNewPoses([this](AugmentedPose const& pose) {
|
||||
if (pose.pose.poseIsValid) {
|
||||
this->lastPose[pose.deviceId] = pose;
|
||||
}
|
||||
});
|
||||
|
||||
if (index >= 0 && index < vr::k_unMaxTrackedDeviceCount) {
|
||||
pose = lastPose[index].pose;
|
||||
if (pSampleTime) *pSampleTime = lastPose[index].sample_time;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPose(int index, const vr::DriverPose_t& pose) {
|
||||
if (index >= vr::k_unMaxTrackedDeviceCount) return;
|
||||
if (pData == nullptr) return;
|
||||
|
||||
AugmentedPose augPose;
|
||||
augPose.deviceId = index;
|
||||
augPose.pose = pose;
|
||||
QueryPerformanceCounter(&augPose.sample_time);
|
||||
|
||||
uint64_t cur_index = pData->index.load(std::memory_order_relaxed) + 1;
|
||||
pData->poses[cur_index % BUFFERED_SAMPLES] = augPose;
|
||||
pData->index.store(cur_index, std::memory_order_release);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,75 +1,52 @@
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/hyblocker/OpenVR-SpaceCalibrator/blob/develop/.github/logo_light.png?raw=true">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://github.com/hyblocker/OpenVR-SpaceCalibrator/blob/develop/.github/logo_dark.png?raw=true">
|
||||
<img alt="Space Calibrator" src="https://github.com/hyblocker/OpenVR-SpaceCalibrator/blob/develop/.github/logo.png?raw=true">
|
||||
</picture>
|
||||
# OpenVR Space Calibrator
|
||||
|
||||
This program is designed to allow you to synchronise multiple playspaces with one another in SteamVR. This fork of Space Calibrator (spacecal) also supports [continuous calibration](#continuous-calibration).
|
||||
This helps you use tracked VR devices from one company with any other. It aligns multiple tracking systems with a quick calibration step. It may not work for your setup, but there are many cases that work to a degree, and some work very well.
|
||||
|
||||
Continuous calibration is a tracking mode which automatically aligns playspaces together, using a tracker on the headset.
|
||||
- Rift CV1 x Vive devices: works very well with the v2 (blue logo) trackers, v1 trackers (grey logo, not in production) have major interference issues in the IR spectrum, controller wands (both gen) and Index controllers work very well.
|
||||
- Rift S, Quest, Windows MR, other SLAM inside-out tracked HMDs x Vive devices: works very well when you aren't moving around the room far (e.g. Beat Saber) but a lot of walking around causes a nontrivial amount of drift between systems. Your results may vary depending on your space. It's possible some of this can be fixed in software with a better calibration algorithm.
|
||||
- Quest wireless streaming is particularly bad right now and requires frequent recalibration, but it does work for a short time until one of many factors causes it to drift. With wireless devices, moving slowly when calibrating and using the Slow or Very Slow calibration modes is effective at reducing the initial error.
|
||||
- Any non-Rift HMD x Touch controllers: does not work, the Oculus driver requires the HMD is a Rift. It's theoretically possible to work around this in software but as far as I know it hasn't been done as it would require a fair amount of reverse engineering effort.
|
||||
|
||||
This version of Space Calibrator 2.0 has been rewritten from the ground up for improved robustness, QOL improvements and less tracking issues, amongst others. For a list of differences compared with other versions, please see [the features list](#features)
|
||||
There is a community of a few thousand on [**Discord**](https://discord.gg/m7g2Wyj) and a newer community on [**Reddit**](https://www.reddit.com/r/MixedVR/). You may find the answer to your question in [the **wiki**](https://github.com/pushrax/OpenVR-SpaceCalibrator/wiki).
|
||||
|
||||
## Installing
|
||||
A quick video of how this works using an old version (~v0.3) is available at https://www.youtube.com/watch?v=W3TnQd9JMl4. The user interface has been upgraded since then; the calibration is now done via a SteamVR dashboard menu, and there's much more configurability.
|
||||
|
||||
### Steam
|
||||
### Install
|
||||
|
||||
> [!NOTE]
|
||||
> **Space Calibrator is also available on Steam.**
|
||||
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.
|
||||
|
||||
You may find [Space Calibrator on Steam here](https://s.team/a/3368750).
|
||||
### Usage
|
||||
|
||||
### From GitHub
|
||||
Once Space Calibrator has a calibration, it works in the background to keep your devices configured correctly. Since v0.8, everything aside from creating the calibration is automated.
|
||||
|
||||
To install Space Calibrator, please get the latest installer from the downloads page, and install it. Make sure that you have:
|
||||
- Installed [Visual C++ Redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe).
|
||||
- Installed SteamVR and run it at least once with a VR headset connected.
|
||||
- SteamVR is not running before you run the installer. If SteamVR is running the installer will not be able to install Space Calibrator correctly.
|
||||
### Calibration
|
||||
|
||||
## Calibration
|
||||
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:
|
||||
|
||||
If you do not wish to use continuous calibration, you will have to use regular calibration. This means that every so often you will have to sync your headset's playspace with your tracker's playspace.
|
||||
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`
|
||||
|
||||
To calibrate:
|
||||
1. Copy the chaperone/guardian bounds from your HMD's play space
|
||||
> You will only have to do this once. Connect your VR headset and start SteamVR. Then go to space calibrator's window (it will be minimised), and click the "Copy Chaperone" button.
|
||||
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.
|
||||
|
||||
2. Open the SteamVR dashboard. At the bottom, click on the Space Calibrator icon.
|
||||
3. In the Space Calibrator overlay, you'll see two lists at the top. On the left `Reference Space` column, select the controller you'll be calibrating along (e.g. Quest controller, Pico controller). On the right `Target Space`, select your SteamVR tracker (e.g. Vive Ultimate Tracker, Vive Tracker 3.0, Vive Ultimate Tracker). You can use the Identify button to make the controllers blink and tracker LEDs flash to see if you've selected the correct ones.
|
||||
4. Click the "Start calibration" button, and start calibrating.
|
||||
### Calibration outside VR
|
||||
|
||||
## Continuous Calibration
|
||||
You can calibrate without using the dashboard overlay by unminimizing Space Calibrator after opening SteamVR (it starts minimized). This is required if you're calibrating for a lone HMD without any devices in its tracking system.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **A tracker attached on your headset is required for this.**
|
||||
### Compiling your own build
|
||||
|
||||
To enable continuous calibration mode, first select your headset on the left column, then the tracker on your headset on the right column. Once you've done so, click `Start Calibration`, and click cancel. Then click `Continuous Calibration` to enable continuous calibration.
|
||||
Open `OpenVR-SpaceCalibrator.sln` in Visual Studio 2017 and build. There are no external dependencies.
|
||||
|
||||
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.
|
||||
### The math
|
||||
|
||||
## Features
|
||||
|
||||
This version has been rewritten from scratch. It shares little code with the original repository but keeps similar ideas.
|
||||
|
||||
Major features:
|
||||
- The UI has been reworked substantially to improve UX. The goal is to reduce the need of tutorials and have the app explain how to calibrate and what one may do to improve calibrations directly in app rather than elsewhere.
|
||||
- Calibrations are now more streamlined. The calibration logic has been simplified to attempt minimising the chances of erroneous data being injected into a calibration sequence yielding poor calibrations.
|
||||
- Calibrations now require you to move around by default. The app will ignore data without enough movement to minimise the chances of poor calibrations arising due to a lack of movement.
|
||||
- Continuous calibration has been improved to reduce the frequency of mis-calibrations as much as possible.
|
||||
- Relative calibrations. The aim is to re-formulate how a calibration is stored so that it is now relative to your headset, meaning that if your headset drifts your trackers would along with it, hiding the drift entirely.
|
||||
- Made calibrating require a minimum amount of movement to ensure that a calibration is valid. This is to improve the success rate of most calibrations.
|
||||
- Robust logging. Logs are saved at `%APPDATA%/space-calibrator/logs` on Windows, and `~/.local/share/space-calibrator/logs` on Linux.
|
||||
- Settings are saved to a JSON file at `%APPDATA%/space-calibrator/config.json` on Windows, and `~/.local/share/space-calibrator/config.json` on Linux.
|
||||
- Linux support. The codebase can now be compiled for Linux x64 amd arm64. Official support assumes Steam Runtime 4, compatibility with other distributions is NOT guaranteed. This is not thoroughly tested but contributions / bug reports are appreciated.
|
||||
- Space Calibration now supports translations. The app will default to showing text in your system language, and you may override it from the Settings page. For guidance regarding contributing translations please see [TRANSLATING.md](https://github.com/hyblocker/OpenVR-SpaceCalibrator/blob/develop/TRANSLATING.md)
|
||||
- The UI renderer has been upgraded and now supports either OpenGL, DirectX11 (on Windows only) or Vulkan 1.3. The app will default to DirectX11 on Windows and Vulkan on Linux. This is to reduce issues for end users on buggy GPU drivers causing the app to fail to launch. You can override the renderer by passing `--renderer <opengl|dx11|vulkan>` as launch arguments.
|
||||
- Base Station management has been integrated into the app to allow you to control them without 3rd party software.
|
||||
- More things that aren't mentioned here.
|
||||
|
||||
## Help
|
||||
|
||||
If you need help with setting up this program, please check the [wiki](https://github.com/pushrax/OpenVR-SpaceCalibrator/wiki), or join the [Discord server](https://discord.gg/ja3WgNjC3z).
|
||||
See [math.pdf](https://github.com/pushrax/OpenVR-SpaceCalibrator/blob/master/math.pdf) for details.
|
||||
If you have some ideas for how to improve the calibration process, let me know!
|
||||
@@ -1,72 +0,0 @@
|
||||
# Translating
|
||||
|
||||
The ground truth translation will always be available at `assets/en_GB.json`. If you notice that something is different there when compared to the language you're translating please use the `en_GB` file as reference.
|
||||
|
||||
Translations are authored in JSON. They're a key value pair of IDs to the text:
|
||||
|
||||
```json
|
||||
{
|
||||
"app_title": "{0} {1}", /* 0 : Space Calibrator ; 1 : version (eg 2.0.0) */
|
||||
"app_title_vr": "{0} {1} - close VR overlay to use mouse", /* 0 : Space Calibrator ; 1 : version (eg 2.0.0) */
|
||||
"tracking_system_no_systems": "No tracked devices present. Please turn on a device to continue.", /* Should never be hit but you never know */
|
||||
|
||||
"ipc_unavailable": "Failed to connect to Space Calibrator SteamVR driver. Please verify file integrity in Properties -> Installed Files, or re-install the app.",
|
||||
"steamvr_unavailable": "Failed to connect to SteamVR.",
|
||||
"vr_troubleshooting_hmd_not_found": "Please connect your VR headset and launch SteamVR first.",
|
||||
"vr_troubleshooting_connect_steamlink": "Please open SteamLink on your VR headset and connect to SteamVR first.",
|
||||
"vr_troubleshooting_generic": "Please connect your VR headset and launch SteamVR first. ({0} {1})", /* 0 : SteamVR init error (eg 108), 1 : SteamVR init error string (eg Headset not connected) - this string is provided by Steam. */
|
||||
|
||||
// space here refers to a tracking space / tracking system.
|
||||
"reference_space": "Reference space",
|
||||
"target_space": "Target space",
|
||||
"select_reference_device": "Select reference device",
|
||||
"select_target_device": "Select target device"
|
||||
}
|
||||
```
|
||||
|
||||
The format supports comments, which are usually used to inform you of what numbered arguments are.
|
||||
|
||||
Text formatting may use `{N}`, where `N` is a numbered argument so that you can re-order text to make grammatical sense in your language of choice.
|
||||
|
||||
For example, you can write: `Arg0: {0} arg1: {1}` ; and itll appear as `Arg0: foo arg1: bar` or `Arg0: {1} arg1: {0}` which appears as `Arg0: bar arg1: foo`.
|
||||
|
||||
You can experiment with translations by making a `json` file matching a locale string at `<SPACECAL-DIR>/assets/lang/en_GB.json`
|
||||
|
||||
Missing strings will fall back to their respective English strings. For example, if you are editing `it.json` and forgot to add the string `reference_space`, the app will show the English value to the user.
|
||||
|
||||
You should also translate the languages block at the bottom of the file. This section is used to present the name of the language to the user in the language selection dropdown in the current language and the language itself. For example, if the user currently has Japanese selected, the entry for French would read as `フランス語 (Français)` in the dropdown.
|
||||
|
||||
The supported languages right now are:
|
||||
- `en_GB.json` (maintained by me, consider it ground truth)
|
||||
- `en_US.json` English (US)
|
||||
- `fr.json` French
|
||||
- `it.json` Italian
|
||||
- `de.json` German
|
||||
- `nl.json` Dutch (missing)
|
||||
- `es_ES.json` Spanish (Spain)
|
||||
- `es_US.json` Spanish (Latin America) (missing)
|
||||
- `da.json` Danish (missing)
|
||||
- `sv.json` Swedish (missing)
|
||||
- `fi.json` Finnish (missing)
|
||||
- `no.json` Norwegian
|
||||
- `bg.json` Bulgarian (missing)
|
||||
- `pl.json` Polish
|
||||
- `cs.json` Czech (missing)
|
||||
- `el.json` Greek (missing)
|
||||
- `hu.json` Hungarian
|
||||
- `pt_PT.json` Portuguese (Portugal) (missing)
|
||||
- `pt_BR.json` Portuguese (Brazil) (missing)
|
||||
- `ro.json` Romanian (missing)
|
||||
- `ru.json` Russian
|
||||
- `tr.json` Turkish (missing)
|
||||
- `uk.json` Ukrainian (missing)
|
||||
- `zh_HANS.json` Chinese (Simplified)
|
||||
- `zh_HANT.json` Chinese (Traditional) (missing)
|
||||
- `ja.json` Japanese
|
||||
- `ko.json` Korean
|
||||
- `th.json` Thai (missing)
|
||||
- `vi.json` Vietnamese (missing)
|
||||
|
||||
These files must live at `<SPACECAL-DIR>/assets/lang/<LANGUAGE>.json`.
|
||||
|
||||
Languages with `(missing)` have not been translated yet. If you know the language, feel free to make a translation. Note that machine generated translations (eg AI, Google Translate) will be rejected indiscriminately.
|
||||
@@ -1,93 +0,0 @@
|
||||
Copyright 2016 The M+ Project Authors.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,93 +0,0 @@
|
||||
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,93 +0,0 @@
|
||||
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,93 +0,0 @@
|
||||
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,93 +0,0 @@
|
||||
Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 252 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 24 KiB |