diff --git a/CMakeLists.txt b/CMakeLists.txt index a6544ea..4aa4bba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,10 @@ cmake_minimum_required(VERSION 3.0.0) -if (NOT DEFINED VCPKG_TARGET_TRIPLET) +#if (NOT DEFINED VCPKG_TARGET_TRIPLET) if(WIN32) set(VCPKG_TARGET_TRIPLET "x64-windows-static-md") endif() -endif() +#endif() # If the toolchain is already defined, do not attempt to find it if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) @@ -65,6 +65,8 @@ find_package(Protobuf CONFIG REQUIRED) protobuf_generate_cpp(PROTO_SRC PROTO_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/src/bridge/ProtobufMessages.proto") SET_SOURCE_FILES_PROPERTIES(${PROTO_SRC} ${PROTO_INCL} PROPERTIES GENERATED TRUE) +find_package(simdjson CONFIG REQUIRED) + # Project file(GLOB_RECURSE HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp") file(GLOB_RECURSE SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") @@ -72,7 +74,7 @@ add_library("${PROJECT_NAME}" SHARED "${HEADERS}" "${SOURCES}" ${PROTO_HEADER} $ target_include_directories("${PROJECT_NAME}" PUBLIC "${OPENVR_INCLUDE_DIR}") target_include_directories("${PROJECT_NAME}" PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/libraries/linalg") target_include_directories("${PROJECT_NAME}" PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src/") -target_link_libraries("${PROJECT_NAME}" PUBLIC "${OPENVR_LIB}" protobuf::libprotoc protobuf::libprotobuf protobuf::libprotobuf-lite) +target_link_libraries("${PROJECT_NAME}" PUBLIC "${OPENVR_LIB}" protobuf::libprotoc protobuf::libprotobuf protobuf::libprotobuf-lite simdjson::simdjson) set_property(TARGET "${PROJECT_NAME}" PROPERTY CXX_STANDARD 17) include_directories(${Protobuf_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/IVRDriver.hpp b/src/IVRDriver.hpp index 88c1ec8..d47e6b6 100644 --- a/src/IVRDriver.hpp +++ b/src/IVRDriver.hpp @@ -3,10 +3,20 @@ #include #include #include +#include #include #include "IVRDevice.hpp" +#include namespace SlimeVRDriver { + class UniverseTranslation { + public: + // TODO: do we want to store this differently? + vr::HmdVector3_t translation; + float yaw; + + static UniverseTranslation parse(simdjson::ondemand::object &obj); + }; typedef std::variant SettingsValue; @@ -63,6 +73,11 @@ namespace SlimeVRDriver { /// OpenVR VRServerDriverHost pointer virtual vr::IVRServerDriverHost* GetDriverHost() = 0; + /// + /// Gets the current UniverseTranslation + /// + virtual std::optional GetCurrentUniverse() = 0; + /// /// Writes a log message /// diff --git a/src/TrackerDevice.cpp b/src/TrackerDevice.cpp index b1150af..1ea26e3 100644 --- a/src/TrackerDevice.cpp +++ b/src/TrackerDevice.cpp @@ -59,6 +59,21 @@ void SlimeVRDriver::TrackerDevice::PositionMessage(messages::Position &position) pose.qRotation.y = position.qy(); pose.qRotation.z = position.qz(); + auto current_universe = GetDriver()->GetCurrentUniverse(); + if (current_universe.has_value()) { + auto trans = current_universe.value(); + + // TODO: set this once, somewhere? + pose.vecWorldFromDriverTranslation[0] = -trans.translation.v[0]; + pose.vecWorldFromDriverTranslation[1] = -trans.translation.v[1]; + pose.vecWorldFromDriverTranslation[2] = -trans.translation.v[2]; + + pose.qWorldFromDriverRotation.w = cos(trans.yaw / 2); + pose.qWorldFromDriverRotation.x = 0; + pose.qWorldFromDriverRotation.y = sin(trans.yaw / 2); + pose.qWorldFromDriverRotation.z = 0; + } + // Post pose GetDriver()->GetDriverHost()->TrackedDevicePoseUpdated(this->device_index_, pose, sizeof(vr::DriverPose_t)); this->last_pose_ = pose; diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index 7174d2c..7486efe 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -3,6 +3,8 @@ #include "bridge/bridge.hpp" #include "TrackerRole.hpp" #include +#include +#include "VRPaths_openvr.hpp" vr::EVRInitError SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext* pDriverContext) @@ -13,9 +15,22 @@ vr::EVRInitError SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext* pDriverCont } Log("Activating SlimeVR Driver..."); + + try { + auto json = simdjson::padded_string::load(GetVRPathRegistryFilename()); // load VR Path Registry + simdjson::ondemand::document doc = json_parser.iterate(json); + auto path = std::string { doc.get_object()["config"].at(0).get_string().value() }; + // Log(path); + default_chap_path_ = GetDefaultChaperoneFromConfigPath(path); + } catch (simdjson::simdjson_error& e) { + std::stringstream ss; + ss << "Error getting VR Config path, continuing: " << e.error(); + Log(ss.str()); + } + Log("SlimeVR Driver Loaded Successfully"); - return vr::VRInitError_None; + return vr::VRInitError_None; } void SlimeVRDriver::VRDriver::Cleanup() @@ -92,12 +107,52 @@ void SlimeVRDriver::VRDriver::RunFrame() Log("Sent HMD hello message"); } + uint64_t universe = vr::VRProperties()->GetUint64Property(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_CurrentUniverseId_Uint64); + if (!current_universe.has_value() || current_universe.value().first != universe) { + auto res = search_universes(universe); + if (res.has_value()) { + current_universe.emplace(universe, res.value()); + } else { + Log("Failed to find current universe!"); + } + } + vr::TrackedDevicePose_t hmd_pose[10]; vr::VRServerDriverHost()->GetRawTrackedDevicePoses(0, hmd_pose, 10); vr::HmdQuaternion_t q = GetRotation(hmd_pose[0].mDeviceToAbsoluteTracking); vr::HmdVector3_t pos = GetPosition(hmd_pose[0].mDeviceToAbsoluteTracking); + if (current_universe.has_value()) { + auto trans = current_universe.value().second; + pos.v[0] += trans.translation.v[0]; + pos.v[1] += trans.translation.v[1]; + pos.v[2] += trans.translation.v[2]; + + // rotate by quaternion w = cos(-trans.yaw / 2), x = 0, y = sin(-trans.yaw / 2), z = 0 + auto tmp_w = cos(-trans.yaw / 2); + auto tmp_y = sin(-trans.yaw / 2); + auto new_w = tmp_w * q.w - tmp_y * q.y; + auto new_x = tmp_w * q.x + tmp_y * q.z; + auto new_y = tmp_w * q.y + tmp_y * q.w; + auto new_z = tmp_w * q.z - tmp_y * q.x; + + q.w = new_w; + q.x = new_x; + q.y = new_y; + q.z = new_z; + + // rotate point on the xz plane by -trans.yaw radians + // this is equivilant to the quaternion multiplication, after applying the double angle formula. + float tmp_sin = sin(-trans.yaw); + float tmp_cos = cos(-trans.yaw); + auto pos_x = pos.v[0] * tmp_cos + pos.v[2] * tmp_sin; + auto pos_z = pos.v[0] * -tmp_sin + pos.v[2] * tmp_cos; + + pos.v[0] = pos_x; + pos.v[2] = pos_z; + } + messages::Position* hmdPosition = google::protobuf::Arena::CreateMessage(&arena); message->set_allocated_position(hmdPosition); @@ -264,3 +319,73 @@ vr::HmdVector3_t SlimeVRDriver::VRDriver::GetPosition(vr::HmdMatrix34_t &matrix) return vector; } + +SlimeVRDriver::UniverseTranslation SlimeVRDriver::UniverseTranslation::parse(simdjson::ondemand::object &obj) { + SlimeVRDriver::UniverseTranslation res; + int iii = 0; + for (auto component: obj["translation"]) { + if (iii > 2) { + break; // TODO: 4 components in a translation vector? should this be an error? + } + res.translation.v[iii] = component.get_double(); + iii += 1; + } + res.yaw = obj["yaw"].get_double(); + + return res; +} + +std::optional SlimeVRDriver::VRDriver::search_universe(std::string path, uint64_t target) { + try { + auto json = simdjson::padded_string::load(path); // load VR Path Registry + simdjson::ondemand::document doc = json_parser.iterate(json); + + for (simdjson::ondemand::object uni: doc["universes"]) { + // TODO: universeID comes after the translation, would it be faster to unconditionally parse the translation? + auto elem = uni["universeID"]; + uint64_t parsed_universe; + + auto is_integer = elem.is_integer(); + if (!is_integer.error() && is_integer.value_unsafe()) { + parsed_universe = elem.get_uint64(); + } else { + parsed_universe = elem.get_uint64_in_string(); + } + + if (parsed_universe == target) { + return SlimeVRDriver::UniverseTranslation::parse(uni["standing"].get_object().value()); + } + } + } catch (simdjson::simdjson_error& e) { + std::stringstream ss; + ss << "Error getting universes from \"" << path << "\": " << e.error(); + Log(ss.str()); + return std::nullopt; + } + + return std::nullopt; +} + +std::optional SlimeVRDriver::VRDriver::search_universes(uint64_t target) { + auto driver_chap_path = vr::VRProperties()->GetStringProperty(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_DriverProvidedChaperonePath_String); + if (driver_chap_path != "") { + auto driver_res = search_universe(driver_chap_path, target); + if (driver_res.has_value()) { + return driver_res.value(); + } + } + + if (default_chap_path_.has_value()) { + return search_universe(default_chap_path_.value(), target); + } + + return std::nullopt; +} + +std::optional SlimeVRDriver::VRDriver::GetCurrentUniverse() { + if (current_universe.has_value()) { + return current_universe.value().second; + } else { + return std::nullopt; + } +} \ No newline at end of file diff --git a/src/VRDriver.hpp b/src/VRDriver.hpp index b54c379..b8b5a47 100644 --- a/src/VRDriver.hpp +++ b/src/VRDriver.hpp @@ -3,12 +3,15 @@ #include #include +#include #include #include #include +#include + namespace SlimeVRDriver { class VRDriver : public IVRDriver { public: @@ -34,6 +37,8 @@ namespace SlimeVRDriver { virtual void LeaveStandby() override; virtual ~VRDriver() = default; + virtual std::optional GetCurrentUniverse() override; + private: std::vector> devices_; std::vector openvr_events_; @@ -47,5 +52,14 @@ namespace SlimeVRDriver { vr::HmdVector3_t GetPosition(vr::HmdMatrix34_t &matrix); bool sentHmdAddMessage = false; + + simdjson::ondemand::parser json_parser; + std::optional default_chap_path_ = std::nullopt; + //std::map universes; + + std::optional> current_universe = std::nullopt; + + std::optional search_universe(std::string path, uint64_t target); + std::optional search_universes(uint64_t target); }; }; \ No newline at end of file diff --git a/src/VRPaths_openvr.cpp b/src/VRPaths_openvr.cpp new file mode 100644 index 0000000..d1d3864 --- /dev/null +++ b/src/VRPaths_openvr.cpp @@ -0,0 +1,262 @@ +// NOTE: the following is an amalgamation of vrpathregistry_public.cpp, strtools_public.cpp, envvartools_public.cpp, and pathtools_public.cpp +// from openvr, to avoid the questions of "Can we already call this somehow?" +// or "How can we directly integrate this in our build system?" +// TODO: Test on OSX/Linux, because our build system is probably not outputting the macros that this wants for them. +// I already had to change WIN32 to _WIN32 for the initial include of windows.h +// TODO: This is licensed under BSD 3-Clause which is compatible with MIT, +// but we should probably do something to ensure we comply with clause 2. + +/* Copyright (c) 2015, Valve Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder 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. + * + */ + +#if defined( _WIN32 ) +#include +#include +#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING + +#undef GetEnvironmentVariable +#elif defined (OSX) +#include +#include +#elif defined(LINUX) +#include +#include +#endif + +#include +#include +#include +#include + + +#ifndef VRLog + #if defined( __MINGW32__ ) + #define VRLog(args...) fprintf(stderr, args) + #elif defined( WIN32 ) + #define VRLog(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) + #else + #define VRLog(args...) fprintf(stderr, args) + #endif +#endif + +typedef std::codecvt_utf8< wchar_t > convert_type; + +std::string UTF16to8(const wchar_t * in) +{ + static std::wstring_convert< convert_type, wchar_t > s_converter; // construction of this can be expensive (or even serialized) depending on locale + + try + { + return s_converter.to_bytes( in ); + } + catch ( ... ) + { + return std::string(); + } +} + +std::string UTF16to8( const std::wstring & in ) { return UTF16to8( in.c_str() ); } + +/** Returns the root of the directory the system wants us to store user config data in */ +static std::string GetAppSettingsPath() +{ +#if defined( WIN32 ) + WCHAR rwchPath[MAX_PATH]; + + if( !SUCCEEDED( SHGetFolderPathW( NULL, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, rwchPath ) ) ) + { + return ""; + } + + // Convert the path to UTF-8 and store in the output + std::string sUserPath = UTF16to8( rwchPath ); + + return sUserPath; +#elif defined( OSX ) + std::string sSettingsDir; + @autoreleasepool { + // Search for the path + NSArray *paths = NSSearchPathForDirectoriesInDomains( NSApplicationSupportDirectory, NSUserDomainMask, YES ); + if ( [paths count] == 0 ) + { + return ""; + } + + NSString *resolvedPath = [paths objectAtIndex:0]; + resolvedPath = [resolvedPath stringByAppendingPathComponent: @"OpenVR"]; + + if ( ![[NSFileManager defaultManager] createDirectoryAtPath: resolvedPath withIntermediateDirectories:YES attributes:nil error:nil] ) + { + return ""; + } + + sSettingsDir.assign( [resolvedPath UTF8String] ); + } + return sSettingsDir; +#elif defined( LINUX ) + + // As defined by XDG Base Directory Specification + // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + + const char *pchHome = getenv("XDG_CONFIG_HOME"); + if ( ( pchHome != NULL) && ( pchHome[0] != '\0' ) ) + { + return pchHome; + } + + // + // XDG_CONFIG_HOME is not defined, use ~/.config instead + // + pchHome = getenv( "HOME" ); + if ( pchHome == NULL ) + { + return ""; + } + + std::string sUserPath( pchHome ); + sUserPath = Path_Join( sUserPath, ".config" ); + return sUserPath; +#else + #warning "Unsupported platform" +#endif +} + +char Path_GetSlash() +{ +#if defined(_WIN32) + return '\\'; +#else + return '/'; +#endif +} + +/** Jams two paths together with the right kind of slash */ +std::string Path_Join( const std::string & first, const std::string & second, char slash = 0 ) +{ + if( slash == 0 ) + slash = Path_GetSlash(); + + // only insert a slash if we don't already have one + std::string::size_type nLen = first.length(); + if( !nLen ) + return second; +#if defined(_WIN32) + if( first.back() == '\\' || first.back() == '/' ) + nLen--; +#else + char last_char = first[first.length()-1]; + if (last_char == '\\' || last_char == '/') + nLen--; +#endif + + return first.substr( 0, nLen ) + std::string( 1, slash ) + second; +} + +/** Fixes the directory separators for the current platform */ +std::string Path_FixSlashes( const std::string & sPath, char slash = 0 ) +{ + if( slash == 0 ) + slash = Path_GetSlash(); + + std::string sFixed = sPath; + for( std::string::iterator i = sFixed.begin(); i != sFixed.end(); i++ ) + { + if( *i == '/' || *i == '\\' ) + *i = slash; + } + + return sFixed; +} + +// --------------------------------------------------------------------------- +// Purpose: Computes the registry filename +// --------------------------------------------------------------------------- +std::string GetOpenVRConfigPath() +{ + std::string sConfigPath = GetAppSettingsPath(); + if( sConfigPath.empty() ) + return ""; + +#if defined( _WIN32 ) || defined( LINUX ) + sConfigPath = Path_Join( sConfigPath, "openvr" ); +#elif defined ( OSX ) + sConfigPath = Path_Join( sConfigPath, ".openvr" ); +#else + #warning "Unsupported platform" +#endif + sConfigPath = Path_FixSlashes( sConfigPath ); + return sConfigPath; +} + +std::string GetEnvironmentVariable( const char *pchVarName ) +{ +#if defined(_WIN32) + char rchValue[32767]; // max size for an env var on Windows + DWORD cChars = GetEnvironmentVariableA( pchVarName, rchValue, sizeof( rchValue ) ); + if( cChars == 0 ) + return ""; + else + return rchValue; +#elif defined(POSIX) + char *pchValue = getenv( pchVarName ); + if( pchValue ) + return pchValue; + else + return ""; +#else +#error "Unsupported Platform" +#endif +} + +std::string GetVRPathRegistryFilename() +{ + std::string sOverridePath = GetEnvironmentVariable( "VR_PATHREG_OVERRIDE" ); + if ( !sOverridePath.empty() ) + return sOverridePath; + + std::string sPath = GetOpenVRConfigPath(); + if ( sPath.empty() ) + return ""; + +#if defined( _WIN32 ) + sPath = Path_Join( sPath, "openvrpaths.vrpath" ); +#elif defined ( POSIX ) + sPath = Path_Join( sPath, "openvrpaths.vrpath" ); +#else + #error "Unsupported platform" +#endif + sPath = Path_FixSlashes( sPath ); + return sPath; +} + +std::string GetDefaultChaperoneFromConfigPath(std::string path) +{ + return Path_Join(path, "chaperone_info.vrchap"); +} \ No newline at end of file diff --git a/src/VRPaths_openvr.hpp b/src/VRPaths_openvr.hpp new file mode 100644 index 0000000..8068c87 --- /dev/null +++ b/src/VRPaths_openvr.hpp @@ -0,0 +1,5 @@ +#pragma once + +std::string GetVRPathRegistryFilename(); + +std::string GetDefaultChaperoneFromConfigPath(std::string path); \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index 1d61dbd..8272723 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -2,6 +2,7 @@ "name": "slimevr-openvr-driver", "version": "0.2.0", "dependencies": [ - "protobuf" + "protobuf", + "simdjson" ] } \ No newline at end of file