diff --git a/CMakeLists.txt b/CMakeLists.txt index 007ffc13..6e1c3a70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,19 @@ elseif(APPLE) set(PLATFORM_NAME "osx") endif() +execute_process( + COMMAND git rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE) + +# Get the latest commit hash +execute_process( + COMMAND git rev-parse HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE) + find_library(OPENVR_LIB openvr_api HINTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/openvr/lib/${PLATFORM_NAME}${PROCESSOR_ARCH}/" NO_DEFAULT_PATH ) add_subdirectory("overlay") @@ -41,6 +54,9 @@ target_include_directories("${OPENGLOVE_PROJECT}" PUBLIC "${OPENVR_INCLUDE_DIR}" target_include_directories("${OPENGLOVE_PROJECT}" PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/") target_link_libraries("${OPENGLOVE_PROJECT}" PUBLIC "${OPENVR_LIB}" setupapi wsock32 ws2_32 bthprops) +target_compile_definitions("${OPENGLOVE_PROJECT}" PRIVATE + "-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"") + source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/include" PREFIX "Header Files" FILES ${HEADERS}) source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/src" PREFIX "Source Files" FILES ${SOURCES}) set_property(TARGET "${OPENGLOVE_PROJECT}" PROPERTY CXX_STANDARD 17) diff --git a/README.md b/README.md index a4a09028..c1051904 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,84 @@ -# OpenGloves Driver +# OpenGloves Driver [![Steam Badge]][Steam] [![Discord Badge]][Discord] -OpenGloves is an OpenVR driver for DIY Virtual Reality Gloves. Using OpenVR's driver interface we are able to provide support for many SteamVR/OpenVR games. +A general purpose **SteamVR** driver with an **[Open Interface]**
+for anyone to connect their **DIY** **VR** *( Glove )* hardware. -## Installation and Usage +--- +**⸢ [Installation] ⸥ ⸢ [Limitations] ⸥ ⸢ [Compatibility] ⸥** -### Download on Steam: -[![Steam Release](https://cdn.discordapp.com/attachments/790676300552994826/845412304219537439/openglovessteam.png)](https://store.steampowered.com/app/1574050/OpenGloves) - * We strongly recommend downloading the driver from Steam, to recieve automatic updates and UI settings. +--- -*Or download the latest on GitHub:* - * https://github.com/LucidVR/opengloves-driver/releases +## Features -**Follow the wiki guide for configuring the driver** -* https://github.com/LucidVR/opengloves-driver/wiki/Configuring-the-Driver -* The driver will not work correctly unless you configure it properly. +- **Haptic Force Feedback** -**Problems?** -* Check [Troubleshooting](https://github.com/LucidVR/opengloves-driver/wiki/Troubleshooting) - * Didn't help? Contact us on the [Community Discord Server](https://discord.gg/lucidvr) -## Building -If you want to use the driver as-is, refer to [Installation and Usage](#Installation-and-Usage). -If you are planning on modifying source files, refer to [BUILDING.md](https://github.com/LucidVR/opengloves-driver/blob/develop/BUILDING.md). +- **Full Finger Tracking**
+ *‹ Beta › Splay Support*
+ *‹ Beta › Individual Joint Support* -## Compatibility -### Compatible Hardware -* [LucidGloves](https://github.com/LucidVR/lucidgloves) - Lucas VRTech -* [Fngrs](https://github.com/danwillm/Fngrs/) - danwillm -* Have your own hardware you want to feature here? Let us know! +- **Tracker / Controller Positioning**
+ *Automatic relative calibration*
+ *Controller ⟷ Glove* -### Compatible Games -* Refer to [Game Compatibility List](https://github.com/LucidVR/opengloves-driver/wiki/Game-Compatibility-List) -* As this is an OpenVR driver, it is strictly compatible with games that take input from the OpenVR API. Only the games in the list above have been tested to work properly. +- **Button / Joystick Inputs**
+ *➜ Trigger | A | B*
+ *➜ Joystick | X | Y | Click* -### Current features included in the driver -* Finger flexion tracking -* Force feedback haptics -* Positioning from controllers + trackers - * Automatic Calibration -* Button/Joystick inputs - * A/B/Menu buttons, Joystick X/Y/Click -* Communication Protocols: - * Serial USB - * Serial over Bluetooth +- **Multiple Communication Protocols**
+ *- Bluetooth Serial*
+ *- Named Pipes*
+ *- Serial USB* -### Planned features -* BLE Communication -* Finger splay tracking -* Vibration haptics +
+### Steam UI + +The **[Steam]** version of **OpenGloves** also comes
+with a **[UI]** for configuring driver related features. + +##### UI Supported Features +- Force Feedback Testing +- Editable Driver Settings +- Automatic Calibration
+ *Controller ⟷ Glove* + +--- ## Contributing -Pull requests are very welcome. For major changes, please open an issue first to discuss what you would like to change. -## Authors +**Pull requests are very welcome.** + +*For major changes, please open an*
+***[Issue]*** *or contact us first to discuss*
+*what you would like to change.* + +--- + +## Credits + +| Author | Discord | +|:------:|:-------:| +| **[Danwillm]** | `danwillm#8254` | +| **[Lucas VRTech]** | `LucidVR#0001` | + + + + +[Steam Badge]: https://img.shields.io/badge/Steam-000000?style=for-the-badge&logo=steam&logoColor=white +[Discord Badge]: https://img.shields.io/badge/Discord-7289DA?style=for-the-badge&logo=discord&logoColor=white + +[Discord]: https://discord.gg/lucidvr +[Steam]: https://store.steampowered.com/app/1574050/OpenGloves + +[Installation]: docs/Installation.md +[Limitations]: docs/Limitations.md +[Compatibility]: docs/Compatibility.md + +[UI]: https://github.com/lucidVR/opengloves-ui +[Open Interface]: https://github.com/LucidVR/opengloves-driver/wiki/Driver-Input -* Danwillm (`danwillm#8254`) -* Lucas VRTech (`LucidVR#0001`) +[Issue]: https://github.com/LucidVR/opengloves-driver/issues -## Discord -https://discord.gg/lucidvr +[Danwillm]: https://github.com/danwillm +[Lucas VRTech]: https://github.com/lucas-vrtech diff --git a/BUILDING.md b/docs/Building.md similarity index 100% rename from BUILDING.md rename to docs/Building.md diff --git a/docs/Compatibility.md b/docs/Compatibility.md new file mode 100644 index 00000000..456dbce6 --- /dev/null +++ b/docs/Compatibility.md @@ -0,0 +1,81 @@ + +# Compatibility + +
+ +## Officially Hardware + +The following projects are known to be compatible with **OpenGloves**. + +- **[LucidGloves]**
+ *by [Lucas VRTech]* + +- **[Fngrs]**
+ *by [danwillm]* + +
+ +**Made your own hardware? Let us know!** + +--- + +## Your Hardware + +While **OpenGloves** is capable of handling
+inputs from a variety of VR controllers,
+it is primarily designed for VR Gloves. + +
+ +#### Custom Hardware + +To make your own hardware compatible with **OpenGloves**,
+please refer to the **[Driver Input]** page, which provides relevant
+information like `Encoding Schemes` & `Communication Methods`. + +
+ +#### LucidVR + +This is our own **[Firmware]** that is compatible with
+**OpenGloves** and runs on `Arduino` / `ESP32` devices. + +--- + +## Games + +**OpenGloves** is strictly compatible with **OpenVR** compatible games. + +
+ +##### Finger Curling + +Is supported in games that work with the **Index** controllers. + +
+ +##### Force Feedback + +Unfortunately, game compatibility
+with this feature is **[More Limited][Game Compatibility]**. + +If you'd like to make your **Game** / **Mod** compatible
+with `Force Feedback`, please refer to the **[Wiki][Integration]**. + + + + + +[LucidGloves]: https://github.com/LucidVR/lucidgloves +[Lucas VRTech]: https://github.com/lucas-vrtech + +[Fngrs]: https://github.com/danwillm/Fngrs/ +[danwillm]: https://github.com/danwillm + +[Driver Input]: https://github.com/LucidVR/opengloves-driver/wiki/Driver-Input + +[Firmware]: https://github.com/LucidVR/lucidgloves/tree/main/firmware/lucidgloves-firmware + +[Game Compatibility]: https://github.com/LucidVR/opengloves-driver/wiki/Game-Compatibility-List + +[Integration]: https://github.com/LucidVR/opengloves-driver/wiki/Integrating-Force-Feedback diff --git a/docs/Installation.md b/docs/Installation.md new file mode 100644 index 00000000..87e98f3e --- /dev/null +++ b/docs/Installation.md @@ -0,0 +1,23 @@ + +# Installation & Usage + +It is strongly recommended to use the **[Steam]** version
+to receive `Automatic Updates` as well as a `Builtin UI`. + +[![Steam Preview]][Steam] + +--- + +## GitHub + +While ***not recommended***, it is possible to install **OpenGloves**
+manually by downloading a **[Release]** and **[Building]** it yourself. + + + + +[Steam]: https://store.steampowered.com/app/1574050/OpenGloves +[Steam Preview]: https://cdn.discordapp.com/attachments/790676300552994826/845412304219537439/openglovessteam.png + +[Release]: https://github.com/LucidVR/opengloves-driver/releases +[Building]: Building.md diff --git a/docs/Limitations.md b/docs/Limitations.md new file mode 100644 index 00000000..228c9e27 --- /dev/null +++ b/docs/Limitations.md @@ -0,0 +1,50 @@ + +# Limitations + +
+ +#### Missing Custom Controller Support + +Many VR titles do not support finger tracking from **custom**
+controllers, requiring the need to emulate controller types. + + +We emulate the index controller to achieve this
+compatibility, which means that we are limited
+to the inputs that the index controller exposes.
+ +It is possible to emulate an index controller while
+providing your own input profiles and bindings,
+but we have chosen not to include that by default
+in the driver, as to preserve compatibility with
+default index controller bindings. + +##### Custom Implementation + +If you want to implement your own device and use the utilities
+that **OpenGloves** provides, such as `Bone Calculations` and
+`Communication`, you will have to implement a custom driver. + +To do this, you have to create your own class derived from [`DeviceDriver`], that
implement `StartingDevice`, `SetupProps`, `HandleInput` & `StoppingDevice`. + +**➔**  An example of a fully custom controller is **[LucidGloveDriver]**
+     *which you are free to adapt to your needs.* + +
+ +#### Dynamic Inputs + +Due to how **OpenVR** works, inputs cannot be set ***dynamically***. + +Our inputs for `Index Controller Emulated Devices` are ***fixed***
+to that of the index controller, and cannot have custom inputs. + +**➔**  *However, you can define your own inputs in a*
+     *custom device with a different input profile.* + + + + +[`DeviceDriver`]: ../src/DeviceDriver/DeviceDriver.cpp + +[LucidGloveDriver]: https://github.com/LucidVR/opengloves-driver/blob/develop/src/DeviceDriver/LucidGloveDriver.cpp diff --git a/include/Bones.h b/include/Bones.h index a898c475..922a86bd 100644 --- a/include/Bones.h +++ b/include/Bones.h @@ -3,79 +3,23 @@ #include #include +#include "Encode/EncodingManager.h" #include "openvr_driver.h" - -enum class HandSkeletonBone : vr::BoneIndex_t { - Root = 0, - Wrist, - Thumb0, - Thumb1, - Thumb2, - Thumb3, - IndexFinger0, - IndexFinger1, - IndexFinger2, - IndexFinger3, - IndexFinger4, - MiddleFinger0, - MiddleFinger1, - MiddleFinger2, - MiddleFinger3, - MiddleFinger4, - RingFinger0, - RingFinger1, - RingFinger2, - RingFinger3, - RingFinger4, - PinkyFinger0, - PinkyFinger1, - PinkyFinger2, - PinkyFinger3, - PinkyFinger4, - AuxThumb, - AuxIndexFinger, - AuxMiddleFinger, - AuxRingFinger, - AuxPinkyFinger, - _Count -}; +#include "Util/AnimLoader.h" const short NUM_BONES = static_cast(HandSkeletonBone::_Count); -extern vr::VRBoneTransform_t rightOpenPose[NUM_BONES]; -extern vr::VRBoneTransform_t leftOpenPose[NUM_BONES]; - -struct Transform { - Transform(); - std::array rotation; - std::array translation; -}; - -struct AnimationData { - AnimationData(); - Transform startTransform; - float startTime; - Transform endTransform; - float endTime; - float fScaled; -}; - -class IModelManager { - public: - virtual bool Load() = 0; - - virtual AnimationData GetAnimationDataByBoneIndex(const HandSkeletonBone& boneIndex, float f) const = 0; - virtual Transform GetTransformByBoneIndex(const HandSkeletonBone& boneIndex) const = 0; -}; - class BoneAnimator { public: explicit BoneAnimator(const std::string& fileName); - void ComputeSkeletonTransforms(vr::VRBoneTransform_t* skeleton, const std::array& flexion, const bool rightHand); + void ComputeSkeletonTransforms(vr::VRBoneTransform_t* skeleton, const VRInputData& inputData, const bool rightHand); static void TransformLeftBone(vr::VRBoneTransform_t& bone, const HandSkeletonBone& boneIndex); + static float GetAverageCurlValue(const std::array& joints); + void LoadDefaultSkeletonByHand(vr::VRBoneTransform_t* skeleton, const bool rightHand); private: - void SetTransformForBone(vr::VRBoneTransform_t& bone, const HandSkeletonBone& boneIndex, const float f, const bool rightHand) const; + void SetTransformForBone( + vr::VRBoneTransform_t& bone, const HandSkeletonBone& boneIndex, const float curl, const float splay, const bool rightHand) const; std::string fileName_; std::unique_ptr modelManager_; diff --git a/include/Communication/SerialCommunicationManager.h b/include/Communication/SerialCommunicationManager.h index a07a5f5c..687528b9 100644 --- a/include/Communication/SerialCommunicationManager.h +++ b/include/Communication/SerialCommunicationManager.h @@ -27,6 +27,12 @@ class SerialCommunicationManager : public CommunicationManager { private: bool PurgeBuffer() const; + bool SetCommunicationTimeout( + unsigned long ReadIntervalTimeout, + unsigned long ReadTotalTimeoutMultiplier, + unsigned long ReadTotalTimeoutConstant, + unsigned long WriteTotalTimeoutMultiplier, + unsigned long WriteTotalTimeoutConstant); VRSerialConfiguration serialConfiguration_; diff --git a/include/DeviceDriver/DeviceDriver.h b/include/DeviceDriver/DeviceDriver.h index 02416f17..74ee67db 100644 --- a/include/DeviceDriver/DeviceDriver.h +++ b/include/DeviceDriver/DeviceDriver.h @@ -35,6 +35,7 @@ class DeviceDriver : public vr::ITrackedDeviceServerDriver { virtual void SetupProps(vr::PropertyContainerHandle_t& props) = 0; virtual void StartingDevice() = 0; virtual void StoppingDevice() = 0; + void PoseUpdateThread(); std::unique_ptr communicationManager_; std::shared_ptr boneAnimator_; @@ -45,6 +46,8 @@ class DeviceDriver : public vr::ITrackedDeviceServerDriver { vr::VRInputComponentHandle_t skeletalComponentHandle_; vr::VRBoneTransform_t handTransforms_[NUM_BONES]; - bool hasActivated_; + std::thread poseUpdateThread_; + + std::atomic hasActivated_; uint32_t driverId_; }; \ No newline at end of file diff --git a/include/Encode/AlphaEncodingManager.h b/include/Encode/AlphaEncodingManager.h index 043df5ef..7adef598 100644 --- a/include/Encode/AlphaEncodingManager.h +++ b/include/Encode/AlphaEncodingManager.h @@ -4,8 +4,8 @@ class AlphaEncodingManager : public EncodingManager { public: - explicit AlphaEncodingManager(float maxAnalogValue); + explicit AlphaEncodingManager(float maxAnalogValue) : EncodingManager(maxAnalogValue){}; - VRInputData Decode(std::string input) override; + VRInputData Decode(const std::string& input) override; std::string Encode(const VRFFBData& input) override; }; \ No newline at end of file diff --git a/include/Encode/EncodingManager.h b/include/Encode/EncodingManager.h index c8f19160..2e1937ea 100644 --- a/include/Encode/EncodingManager.h +++ b/include/Encode/EncodingManager.h @@ -7,8 +7,9 @@ #include "DriverLog.h" struct VRFFBData { - VRFFBData(); - VRFFBData(short thumbCurl, short indexCurl, short middleCurl, short ringCurl, short pinkyCurl); + VRFFBData() : VRFFBData(0, 0, 0, 0, 0){}; + VRFFBData(short thumbCurl, short indexCurl, short middleCurl, short ringCurl, short pinkyCurl) + : thumbCurl(thumbCurl), indexCurl(indexCurl), middleCurl(middleCurl), ringCurl(ringCurl), pinkyCurl(pinkyCurl){}; const short thumbCurl; const short indexCurl; @@ -18,7 +19,10 @@ struct VRFFBData { }; struct VRInputData { - VRInputData(); + VRInputData() + : VRInputData( + {0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, 0.0f, 0.0f, false, false, false, false, false, false, false, false){}; + VRInputData( std::array flexion, float joyX, @@ -30,9 +34,68 @@ struct VRInputData { bool grab, bool pinch, bool menu, - bool calibrate); + bool calibrate) + : flexion({ + flexion[0], + flexion[0], + flexion[0], + flexion[0], + flexion[1], + flexion[1], + flexion[1], + flexion[1], + flexion[2], + flexion[2], + flexion[2], + flexion[2], + flexion[3], + flexion[3], + flexion[3], + flexion[3], + flexion[4], + flexion[4], + flexion[4], + flexion[4], + }), + joyX(joyX), + joyY(joyY), + joyButton(joyButton), + trgButton(trgButton), + aButton(aButton), + bButton(bButton), + grab(grab), + pinch(pinch), + menu(menu), + calibrate(calibrate) {} + + VRInputData( + std::array, 5> flexion, + std::array splay, + float joyX, + float joyY, + bool joyButton, + bool trgButton, + bool aButton, + bool bButton, + bool grab, + bool pinch, + bool menu, + bool calibrate) + : flexion(flexion), + splay(splay), + joyX(joyX), + joyY(joyY), + joyButton(joyButton), + trgButton(trgButton), + aButton(aButton), + bButton(bButton), + grab(grab), + pinch(pinch), + menu(menu), + calibrate(calibrate) {} - const std::array flexion; + const std::array, 5> flexion; + const std::array splay = {-2.0f, -2.0f, -2.0f, -2.0f, -2.0f}; const float joyX; const float joyY; const bool joyButton; @@ -47,8 +110,8 @@ struct VRInputData { class EncodingManager { public: - explicit EncodingManager(float maxAnalogValue); - virtual VRInputData Decode(std::string input) = 0; + explicit EncodingManager(float maxAnalogValue) : maxAnalogValue_(maxAnalogValue){}; + virtual VRInputData Decode(const std::string& input) = 0; virtual std::string Encode(const VRFFBData& data) = 0; protected: diff --git a/include/Encode/LegacyEncodingManager.h b/include/Encode/LegacyEncodingManager.h index 672d806b..7f92ae64 100644 --- a/include/Encode/LegacyEncodingManager.h +++ b/include/Encode/LegacyEncodingManager.h @@ -6,6 +6,6 @@ class LegacyEncodingManager : public EncodingManager { public: explicit LegacyEncodingManager(float maxAnalogValue); - VRInputData Decode(std::string input) override; + VRInputData Decode(const std::string& input) override; std::string Encode(const VRFFBData& input) override; }; \ No newline at end of file diff --git a/include/Util/AnimLoader.h b/include/Util/AnimLoader.h new file mode 100644 index 00000000..1cd9de3e --- /dev/null +++ b/include/Util/AnimLoader.h @@ -0,0 +1,97 @@ +#pragma once + +#include + +#include "openvr_driver.h" + +#define TINYGLTF_NO_STB_IMAGE +#define TINYGLTF_NO_STB_IMAGE_WRITE +#include "tiny_gltf.h" + +enum class HandSkeletonBone : vr::BoneIndex_t { + Root = 0, + Wrist, + Thumb0, + Thumb1, + Thumb2, + Thumb3, + IndexFinger0, + IndexFinger1, + IndexFinger2, + IndexFinger3, + IndexFinger4, + MiddleFinger0, + MiddleFinger1, + MiddleFinger2, + MiddleFinger3, + MiddleFinger4, + RingFinger0, + RingFinger1, + RingFinger2, + RingFinger3, + RingFinger4, + PinkyFinger0, + PinkyFinger1, + PinkyFinger2, + PinkyFinger3, + PinkyFinger4, + AuxThumb, + AuxIndexFinger, + AuxMiddleFinger, + AuxRingFinger, + AuxPinkyFinger, + _Count +}; + +struct Transform { + Transform(); + std::array rotation; + std::array translation; +}; + +struct AnimationData { + AnimationData(); + Transform startTransform; + float startTime; + Transform endTransform; + float endTime; +}; + +class IModelManager { + public: + virtual bool Load() = 0; + + virtual AnimationData GetAnimationDataByBoneIndex(const HandSkeletonBone& boneIndex, float f) const = 0; + virtual Transform GetTransformByBoneIndex(const HandSkeletonBone& boneIndex) const = 0; +}; + +class GLTFModelManager : public IModelManager { + public: + GLTFModelManager(std::string fileName) : fileName_(std::move(fileName)) {} + + bool Load() override; + + AnimationData GetAnimationDataByBoneIndex(const HandSkeletonBone& boneIndex, const float f) const override; + Transform GetTransformByBoneIndex(const HandSkeletonBone& boneIndex) const; + + private: + void LoadInitialTransforms(); + void LoadKeyframeTimes(); + + template + std::vector> GetVecN(const tinygltf::Accessor& accessor) const { + const tinygltf::BufferView bufferView = model_.bufferViews[accessor.bufferView]; + const std::vector& bufData = model_.buffers[0].data; + + std::vector> res(accessor.count); + memcpy(&res[0], bufData.data() + bufferView.byteOffset + accessor.byteOffset, accessor.count * sizeof(float) * N); + + return res; + } + + tinygltf::Model model_; + std::string fileName_; + std::vector initialTransforms_; + std::vector keyframeTimes_; + std::vector> keyframeTransforms_; +}; \ No newline at end of file diff --git a/include/Util/Quaternion.h b/include/Util/Quaternion.h index 237d9f31..3b07951d 100644 --- a/include/Util/Quaternion.h +++ b/include/Util/Quaternion.h @@ -12,10 +12,15 @@ vr::HmdVector3_t CombinePosition(const vr::HmdMatrix34_t& matrix, const vr::HmdV // returns the result of multiplying two quaternions, effectively applying a rotation on a quaternion vr::HmdQuaternion_t MultiplyQuaternion(const vr::HmdQuaternion_t& q, const vr::HmdQuaternion_t& r); +vr::HmdQuaternionf_t MultiplyQuaternion(const vr::HmdQuaternionf_t& q, const vr::HmdQuaternionf_t& r); +vr::HmdQuaternionf_t EulerToQuaternion(const float& yaw, const float& pitch, const float& roll); vr::HmdQuaternion_t EulerToQuaternion(const double& yaw, const double& pitch, const double& roll); + vr::HmdMatrix33_t GetRotationMatrix(const vr::HmdMatrix34_t& matrix); + vr::HmdVector3_t MultiplyMatrix(const vr::HmdMatrix33_t& matrix, const vr::HmdVector3_t& vector); + vr::HmdMatrix33_t QuaternionToMatrix(const vr::HmdQuaternion_t& q); vr::HmdQuaternion_t QuatConjugate(const vr::HmdQuaternion_t& q); diff --git a/src/Bones.cpp b/src/Bones.cpp index 8355e95a..9c1ee58a 100644 --- a/src/Bones.cpp +++ b/src/Bones.cpp @@ -1,65 +1,32 @@ #include "Bones.h" +#include #include #include "DriverLog.h" +#include "Util/Quaternion.h" -#define TINYGLTF_IMPLEMENTATION -#define TINYGLTF_NO_STB_IMAGE -#define TINYGLTF_NO_STB_IMAGE_WRITE -#include "tiny_gltf.h" +static const float c_maxSplayAngle = 10.0f; static const std::array emptyRotation = {0.0f, 0.0f, 0.0f, 0.0f}; static const std::array emptyTranslation = {0.0f, 0.0f, 0.0f}; -Transform::Transform() : rotation(emptyRotation), translation(emptyTranslation) {} -AnimationData::AnimationData() : startTime(0.0f), endTime(0.0f) {} - static float Lerp(const float& a, const float& b, const float& f) { return a + f * (b - a); } enum class FingerIndex : int { Thumb = 0, IndexFinger, MiddleFinger, RingFinger, PinkyFinger, Unknown = -1 }; -static std::map GLTFNodeBoneMap{ - {"REF:Root", HandSkeletonBone::Root}, - {"REF:wrist_r", HandSkeletonBone::Wrist}, - {"REF:finger_thumb_0_r", HandSkeletonBone::Thumb0}, - {"REF:finger_thumb_1_r", HandSkeletonBone::Thumb1}, - {"REF:finger_thumb_2_r", HandSkeletonBone::Thumb2}, - {"REF:finger_thumb_r_end", HandSkeletonBone::Thumb3}, - {"REF:finger_index_meta_r", HandSkeletonBone::IndexFinger0}, - {"REF:finger_index_0_r", HandSkeletonBone::IndexFinger1}, - {"REF:finger_index_1_r", HandSkeletonBone::IndexFinger2}, - {"REF:finger_index_2_r", HandSkeletonBone::IndexFinger3}, - {"REF:finger_index_r_end", HandSkeletonBone::IndexFinger4}, - {"REF:finger_middle_meta_r", HandSkeletonBone::MiddleFinger0}, - {"REF:finger_middle_0_r", HandSkeletonBone::MiddleFinger1}, - {"REF:finger_middle_1_r", HandSkeletonBone::MiddleFinger2}, - {"REF:finger_middle_2_r", HandSkeletonBone::MiddleFinger3}, - {"REF:finger_middle_r_end", HandSkeletonBone::MiddleFinger4}, - {"REF:finger_ring_meta_r", HandSkeletonBone::RingFinger0}, - {"REF:finger_ring_0_r", HandSkeletonBone::RingFinger1}, - {"REF:finger_ring_1_r", HandSkeletonBone::RingFinger2}, - {"REF:finger_ring_2_r", HandSkeletonBone::RingFinger3}, - {"REF:finger_ring_r_end", HandSkeletonBone::RingFinger4}, - {"REF:finger_pinky_meta_r", HandSkeletonBone::PinkyFinger0}, - {"REF:finger_pinky_0_r", HandSkeletonBone::PinkyFinger1}, - {"REF:finger_pinky_1_r", HandSkeletonBone::PinkyFinger2}, - {"REF:finger_pinky_2_r", HandSkeletonBone::PinkyFinger3}, - {"REF:finger_pinky_r_end", HandSkeletonBone::PinkyFinger4}, - {"REF:finger_thumb_r_aux", HandSkeletonBone::AuxThumb}, - {"REF:finger_index_r_aux", HandSkeletonBone::AuxIndexFinger}, - {"REF:finger_middle_r_aux", HandSkeletonBone::AuxMiddleFinger}, - {"REF:finger_ring_r_aux", HandSkeletonBone::AuxRingFinger}, - {"REF:finger_pinky_r_aux", HandSkeletonBone::AuxPinkyFinger}}; - -static FingerIndex GetFingerFromBoneIndex(const HandSkeletonBone bone) { +static bool IsBoneSplayableBone(const HandSkeletonBone& bone) { + return bone == HandSkeletonBone::Thumb0 || bone == HandSkeletonBone::IndexFinger1 || bone == HandSkeletonBone::MiddleFinger1 || + bone == HandSkeletonBone::RingFinger1 || bone == HandSkeletonBone::PinkyFinger1; +} + +static FingerIndex GetFingerFromBoneIndex(const HandSkeletonBone& bone) { switch (bone) { case HandSkeletonBone::Thumb0: case HandSkeletonBone::Thumb1: case HandSkeletonBone::Thumb2: - case HandSkeletonBone::Thumb3: case HandSkeletonBone::AuxThumb: return FingerIndex::Thumb; @@ -67,7 +34,6 @@ static FingerIndex GetFingerFromBoneIndex(const HandSkeletonBone bone) { case HandSkeletonBone::IndexFinger1: case HandSkeletonBone::IndexFinger2: case HandSkeletonBone::IndexFinger3: - case HandSkeletonBone::IndexFinger4: case HandSkeletonBone::AuxIndexFinger: return FingerIndex::IndexFinger; @@ -75,7 +41,6 @@ static FingerIndex GetFingerFromBoneIndex(const HandSkeletonBone bone) { case HandSkeletonBone::MiddleFinger1: case HandSkeletonBone::MiddleFinger2: case HandSkeletonBone::MiddleFinger3: - case HandSkeletonBone::MiddleFinger4: case HandSkeletonBone::AuxMiddleFinger: return FingerIndex::MiddleFinger; @@ -83,7 +48,6 @@ static FingerIndex GetFingerFromBoneIndex(const HandSkeletonBone bone) { case HandSkeletonBone::RingFinger1: case HandSkeletonBone::RingFinger2: case HandSkeletonBone::RingFinger3: - case HandSkeletonBone::RingFinger4: case HandSkeletonBone::AuxRingFinger: return FingerIndex::RingFinger; @@ -91,7 +55,6 @@ static FingerIndex GetFingerFromBoneIndex(const HandSkeletonBone bone) { case HandSkeletonBone::PinkyFinger1: case HandSkeletonBone::PinkyFinger2: case HandSkeletonBone::PinkyFinger3: - case HandSkeletonBone::PinkyFinger4: case HandSkeletonBone::AuxPinkyFinger: return FingerIndex::PinkyFinger; @@ -100,164 +63,93 @@ static FingerIndex GetFingerFromBoneIndex(const HandSkeletonBone bone) { } } -class GLTFModelManager : public IModelManager { - tinygltf::Model model_; - std::string fileName_; - std::vector initialTransforms_; - std::vector keyframeTimes_; - std::vector> keyframeTransforms_; - - public: - GLTFModelManager(std::string fileName) : fileName_(std::move(fileName)) {} - - bool Load() override { - tinygltf::TinyGLTF loader; - std::string err; - std::string warn; - - const bool ret = loader.LoadBinaryFromFile(&model_, &err, &warn, fileName_); +static HandSkeletonBone GetRootFingerBoneFromFingerIndex(const FingerIndex& finger) { + switch (finger) { + case FingerIndex::Thumb: + return HandSkeletonBone::Thumb0; + case FingerIndex::IndexFinger: + return HandSkeletonBone::IndexFinger0; + case FingerIndex::MiddleFinger: + return HandSkeletonBone::MiddleFinger0; + case FingerIndex::RingFinger: + return HandSkeletonBone::RingFinger0; + case FingerIndex::PinkyFinger: + return HandSkeletonBone::PinkyFinger0; + } +} - if (!warn.empty()) { - DriverLog("Warning parsing gltf file: %s", warn.c_str()); - return false; +void BoneAnimator::TransformLeftBone(vr::VRBoneTransform_t& bone, const HandSkeletonBone& boneIndex) { + switch (boneIndex) { + case HandSkeletonBone::Root: { + return; } - - if (!err.empty()) { - DriverLog("Error parsing gltf file: %s", err.c_str()); - return false; + case HandSkeletonBone::Thumb0: + case HandSkeletonBone::IndexFinger0: + case HandSkeletonBone::MiddleFinger0: + case HandSkeletonBone::RingFinger0: + case HandSkeletonBone::PinkyFinger0: { + const vr::HmdQuaternionf_t quat = bone.orientation; + bone.orientation.w = -quat.x; + bone.orientation.x = quat.w; + bone.orientation.y = -quat.z; + bone.orientation.z = quat.y; + break; } - - if (!ret) { - DriverLog("Failed to parse gltf"); - return false; + case HandSkeletonBone::Wrist: + case HandSkeletonBone::AuxIndexFinger: + case HandSkeletonBone::AuxThumb: + case HandSkeletonBone::AuxMiddleFinger: + case HandSkeletonBone::AuxRingFinger: + case HandSkeletonBone::AuxPinkyFinger: { + bone.orientation.y *= -1; + bone.orientation.z *= -1; + break; } - - initialTransforms_ = std::vector(GLTFNodeBoneMap.size()); - keyframeTransforms_ = std::vector>(GLTFNodeBoneMap.size()); - - LoadKeyframeTimes(); - LoadInitialTransforms(); - - return true; - } - - AnimationData GetAnimationDataByBoneIndex(const HandSkeletonBone& boneIndex, const float f) const override { - const float smallest = keyframeTimes_.at(0); - const float largest = keyframeTimes_.at(keyframeTimes_.size() - 1); - const float fScaled = Lerp(smallest, largest, f); - - const size_t lowerKeyframeIndex = std::upper_bound(keyframeTimes_.begin(), keyframeTimes_.end(), fScaled) - keyframeTimes_.begin() - 1; - const size_t upperKeyframeIndex = lowerKeyframeIndex < keyframeTimes_.size() - 1 ? lowerKeyframeIndex + 1 : lowerKeyframeIndex; - - AnimationData result; - result.startTransform = keyframeTransforms_[static_cast(boneIndex)][lowerKeyframeIndex]; - result.startTime = keyframeTimes_[lowerKeyframeIndex]; - result.endTransform = keyframeTransforms_[static_cast(boneIndex)][upperKeyframeIndex]; - result.endTime = keyframeTimes_[upperKeyframeIndex]; - result.fScaled = fScaled; - - return result; - } - - Transform GetTransformByBoneIndex(const HandSkeletonBone& boneIndex) const override { - return initialTransforms_[static_cast(boneIndex)]; - } - - private: - void LoadInitialTransforms() { - for (size_t nodeIndex = 0; nodeIndex < model_.nodes.size(); nodeIndex++) { - const tinygltf::Node& node = model_.nodes[nodeIndex]; - - try { - const int boneIndex = static_cast(GLTFNodeBoneMap.at(node.name)); - - Transform transform; - if (node.rotation.size() >= 4) { - transform.rotation[0] = static_cast(node.rotation[0]); - transform.rotation[1] = static_cast(node.rotation[1]); - transform.rotation[2] = static_cast(node.rotation[2]); - transform.rotation[3] = static_cast(node.rotation[3]); - } - if (node.translation.size() >= 3) { - transform.translation[0] = static_cast(node.translation[0]); - transform.translation[1] = static_cast(node.translation[1]); - transform.translation[2] = static_cast(node.translation[2]); - } - - initialTransforms_[boneIndex] = transform; - - const tinygltf::Animation& animation = model_.animations[0]; - std::vector& transforms = keyframeTransforms_[boneIndex]; - - transforms.resize(keyframeTimes_.size()); - - for (auto& channel : animation.channels) { - if (channel.target_node != nodeIndex) continue; - - const tinygltf::Accessor& accessor = model_.accessors[animation.samplers[channel.sampler].output]; - switch (accessor.type) { - // rotation via quaternion - case TINYGLTF_TYPE_VEC4: { - std::vector> keyframes = GetVecN<4>(accessor); - for (size_t i = 0; i < keyframes.size(); i++) transforms[i].rotation = keyframes[i]; - break; - } - // translation - case TINYGLTF_TYPE_VEC3: { - std::vector> keyframes = GetVecN<3>(accessor); - for (size_t i = 0; i < keyframes.size(); i++) transforms[i].translation = keyframes[i]; - break; - } - } - } - - } catch (const std::out_of_range&) { - DriverLog("Not parsing node as it was not defined as a bone: %i", nodeIndex); - continue; - } + default: { + bone.position.v[1] *= -1; + bone.position.v[2] *= -1; } } - void LoadKeyframeTimes() { - const tinygltf::Accessor accessor = model_.accessors[0]; - keyframeTimes_.resize(accessor.count); - - const tinygltf::BufferView bufferView = model_.bufferViews[accessor.bufferView]; - const std::vector& bufData = model_.buffers[0].data; - memcpy(&keyframeTimes_[0], bufData.data() + bufferView.byteOffset + accessor.byteOffset, accessor.count * sizeof(float)); - } - - template - std::vector> GetVecN(const tinygltf::Accessor& accessor) const { - const tinygltf::BufferView bufferView = model_.bufferViews[accessor.bufferView]; - const std::vector& bufData = model_.buffers[0].data; - - std::vector> res(accessor.count); - memcpy(&res[0], bufData.data() + bufferView.byteOffset + accessor.byteOffset, accessor.count * sizeof(float) * N); + bone.position.v[0] *= -1; +} - return res; - } -}; +static bool IsAuxBone(const HandSkeletonBone& boneIndex) { + return boneIndex == HandSkeletonBone::AuxThumb || boneIndex == HandSkeletonBone::AuxIndexFinger || boneIndex == HandSkeletonBone::AuxMiddleFinger || + boneIndex == HandSkeletonBone::AuxRingFinger || boneIndex == HandSkeletonBone::AuxPinkyFinger; +} BoneAnimator::BoneAnimator(const std::string& fileName) : fileName_(fileName) { modelManager_ = std::make_unique(fileName); loaded_ = modelManager_->Load(); } -void BoneAnimator::ComputeSkeletonTransforms(vr::VRBoneTransform_t* skeleton, const std::array& flexion, const bool rightHand) { +void BoneAnimator::ComputeSkeletonTransforms(vr::VRBoneTransform_t* skeleton, const VRInputData& inputData, const bool rightHand) { if (!loaded_) return; - for (size_t i = 0; i < NUM_BONES; i++) { + for (size_t i = 1; i < NUM_BONES; i++) { const FingerIndex finger = GetFingerFromBoneIndex(static_cast(i)); - if (finger != FingerIndex::Unknown) { - const float f = flexion[static_cast(finger)]; - SetTransformForBone(skeleton[i], static_cast(i), f, rightHand); - } + const int iFinger = static_cast(finger); + + if (finger == FingerIndex::Unknown) continue; + + float curl; + if (IsAuxBone(static_cast(i))) + curl = GetAverageCurlValue(inputData.flexion[iFinger]); + else + curl = inputData.flexion[iFinger][i - static_cast(GetRootFingerBoneFromFingerIndex(finger))]; + + const float splay = inputData.splay[iFinger]; + + SetTransformForBone(skeleton[i], static_cast(i), curl, splay, rightHand); } } -void BoneAnimator::SetTransformForBone(vr::VRBoneTransform_t& bone, const HandSkeletonBone& boneIndex, const float f, const bool rightHand) const { - if (f < 0.0f || f > 1.0f) return; // skip if the value is invalid +// splay asssumesthat there is a valid curl value for the finger +void BoneAnimator::SetTransformForBone( + vr::VRBoneTransform_t& bone, const HandSkeletonBone& boneIndex, const float curl, const float splay, const bool rightHand) const { + // We don't clamp this, as chances are if it's invalid we don't really want to use it anyway. + if (curl < 0.0f || curl > 1.0f) return; const Transform nodeTransform = modelManager_->GetTransformByBoneIndex(boneIndex); bone.orientation.x = nodeTransform.rotation[0]; @@ -268,17 +160,15 @@ void BoneAnimator::SetTransformForBone(vr::VRBoneTransform_t& bone, const HandSk bone.position.v[1] = nodeTransform.translation[1]; bone.position.v[2] = nodeTransform.translation[2]; - const AnimationData animationData = modelManager_->GetAnimationDataByBoneIndex(boneIndex, f); + const AnimationData animationData = modelManager_->GetAnimationDataByBoneIndex(boneIndex, curl); - // start and end time can be the same (if we've reached the max keyframe), so make sure we only do the lerp if not - const float diff = animationData.endTime - animationData.startTime; - const float interp = diff != 0.0f ? (animationData.fScaled - animationData.startTime) / diff : 1.0f; + const float interp = std::clamp((curl - animationData.startTime) / (animationData.endTime - animationData.startTime), 0.0f, 1.0f); if (animationData.startTransform.rotation != emptyRotation) { - bone.orientation.x = Lerp(animationData.startTransform.rotation[0], animationData.endTransform.rotation[0], interp); - bone.orientation.y = Lerp(animationData.startTransform.rotation[1], animationData.endTransform.rotation[1], interp); - bone.orientation.z = Lerp(animationData.startTransform.rotation[2], animationData.endTransform.rotation[2], interp); - bone.orientation.w = Lerp(animationData.startTransform.rotation[3], animationData.endTransform.rotation[3], interp); + bone.orientation.w = Lerp(animationData.startTransform.rotation[0], animationData.endTransform.rotation[0], interp); + bone.orientation.x = Lerp(animationData.startTransform.rotation[1], animationData.endTransform.rotation[1], interp); + bone.orientation.y = Lerp(animationData.startTransform.rotation[2], animationData.endTransform.rotation[2], interp); + bone.orientation.z = Lerp(animationData.startTransform.rotation[3], animationData.endTransform.rotation[3], interp); } if (animationData.startTransform.translation != emptyTranslation) { @@ -288,110 +178,38 @@ void BoneAnimator::SetTransformForBone(vr::VRBoneTransform_t& bone, const HandSk } bone.position.v[3] = 1.0f; + if (splay >= -1.0f && splay <= 1.0f) { + // only splay one bone (all the rest are done relative to this one) + if (IsBoneSplayableBone(boneIndex)) + bone.orientation = MultiplyQuaternion(bone.orientation, EulerToQuaternion(0.0f, static_cast(DegToRad(splay * c_maxSplayAngle)), 0.0f)); + } + + // we're guaranteed to have updated the bone, so we can safely apply a transformation if (!rightHand) TransformLeftBone(bone, boneIndex); }; -void BoneAnimator::TransformLeftBone(vr::VRBoneTransform_t& bone, const HandSkeletonBone& boneIndex) { - switch (boneIndex) { - case HandSkeletonBone::Root: { - return; - } - case HandSkeletonBone::Thumb0: - case HandSkeletonBone::IndexFinger0: - case HandSkeletonBone::MiddleFinger0: - case HandSkeletonBone::RingFinger0: - case HandSkeletonBone::PinkyFinger0: { - const vr::HmdQuaternionf_t quat = bone.orientation; - bone.orientation.w = -quat.x; - bone.orientation.x = quat.w; - bone.orientation.y = -quat.z; - bone.orientation.z = quat.y; - break; - } - case HandSkeletonBone::Wrist: - case HandSkeletonBone::AuxIndexFinger: - case HandSkeletonBone::AuxThumb: - case HandSkeletonBone::AuxMiddleFinger: - case HandSkeletonBone::AuxRingFinger: - case HandSkeletonBone::AuxPinkyFinger: { - bone.orientation.y *= -1; - bone.orientation.z *= -1; - break; - } - default: { - bone.position.v[1] *= -1; - bone.position.v[2] *= -1; - } +float BoneAnimator::GetAverageCurlValue(const std::array& joints) { + float acc = 0; + for (int i = 0; i < joints.size(); i++) { + acc += joints[i]; } - bone.position.v[0] *= -1; + return acc / static_cast(joints.size()); } -// Initial values for the right/left poses -vr::VRBoneTransform_t rightOpenPose[NUM_BONES] = { - {{0.000000f, 0.000000f, 0.000000f, 1.000000f}, {1.000000f, -0.000000f, -0.000000f, 0.000000f}}, - {{0.034038f, 0.036503f, 0.164722f, 1.000000f}, {-0.055147f, -0.078608f, 0.920279f, -0.379296f}}, - {{0.012083f, 0.028070f, 0.025050f, 1.000000f}, {0.567418f, -0.464112f, 0.623374f, -0.272106f}}, - {{-0.040406f, -0.000000f, 0.000000f, 1.000000f}, {0.994838f, 0.082939f, 0.019454f, 0.055130f}}, - {{-0.032517f, -0.000000f, -0.000000f, 1.000000f}, {0.974793f, -0.003213f, 0.021867f, -0.222015f}}, - {{-0.030464f, 0.000000f, 0.000000f, 1.000000f}, {1.000000f, -0.000000f, -0.000000f, 0.000000f}}, - {{-0.000632f, 0.026866f, 0.015002f, 1.000000f}, {0.421979f, -0.644251f, 0.422133f, 0.478202f}}, - {{-0.074204f, 0.005002f, -0.000234f, 1.000000f}, {0.995332f, 0.007007f, -0.039124f, 0.087949f}}, - {{-0.043930f, 0.000000f, 0.000000f, 1.000000f}, {0.997891f, 0.045808f, 0.002142f, -0.045943f}}, - {{-0.028695f, -0.000000f, -0.000000f, 1.000000f}, {0.999649f, 0.001850f, -0.022782f, -0.013409f}}, - {{-0.022821f, -0.000000f, 0.000000f, 1.000000f}, {1.000000f, -0.000000f, 0.000000f, -0.000000f}}, - {{-0.002177f, 0.007120f, 0.016319f, 1.000000f}, {0.541276f, -0.546723f, 0.460749f, 0.442520f}}, - {{-0.070953f, -0.000779f, -0.000997f, 1.000000f}, {0.980294f, -0.167261f, -0.078959f, 0.069368f}}, - {{-0.043108f, -0.000000f, -0.000000f, 1.000000f}, {0.997947f, 0.018493f, 0.013192f, 0.059886f}}, - {{-0.033266f, -0.000000f, -0.000000f, 1.000000f}, {0.997394f, -0.003328f, -0.028225f, -0.066315f}}, - {{-0.025892f, 0.000000f, -0.000000f, 1.000000f}, {0.999195f, -0.000000f, 0.000000f, 0.040126f}}, - {{-0.000513f, -0.006545f, 0.016348f, 1.000000f}, {0.550143f, -0.516692f, 0.429888f, 0.495548f}}, - {{-0.065876f, -0.001786f, -0.000693f, 1.000000f}, {0.990420f, -0.058696f, -0.101820f, 0.072495f}}, - {{-0.040697f, -0.000000f, -0.000000f, 1.000000f}, {0.999545f, -0.002240f, 0.000004f, 0.030081f}}, - {{-0.028747f, 0.000000f, 0.000000f, 1.000000f}, {0.999102f, -0.000721f, -0.012693f, 0.040420f}}, - {{-0.022430f, 0.000000f, -0.000000f, 1.000000f}, {1.000000f, 0.000000f, 0.000000f, 0.000000f}}, - {{0.002478f, -0.018981f, 0.015214f, 1.000000f}, {0.523940f, -0.526918f, 0.326740f, 0.584025f}}, - {{-0.062878f, -0.002844f, -0.000332f, 1.000000f}, {0.986609f, -0.059615f, -0.135163f, 0.069132f}}, - {{-0.030220f, -0.000000f, -0.000000f, 1.000000f}, {0.994317f, 0.001896f, -0.000132f, 0.106446f}}, - {{-0.018187f, -0.000000f, -0.000000f, 1.000000f}, {0.995931f, -0.002010f, -0.052079f, -0.073526f}}, - {{-0.018018f, -0.000000f, 0.000000f, 1.000000f}, {1.000000f, 0.000000f, 0.000000f, 0.000000f}}, - {{0.006059f, 0.056285f, 0.060064f, 1.000000f}, {0.737238f, 0.202745f, -0.594267f, -0.249441f}}, - {{0.040416f, -0.043018f, 0.019345f, 1.000000f}, {-0.290331f, 0.623527f, 0.663809f, 0.293734f}}, - {{0.039354f, -0.075674f, 0.047048f, 1.000000f}, {-0.187047f, 0.678062f, 0.659285f, 0.265683f}}, - {{0.038340f, -0.090987f, 0.082579f, 1.000000f}, {-0.183037f, 0.736793f, 0.634757f, 0.143936f}}, - {{0.031806f, -0.087214f, 0.121015f, 1.000000f}, {-0.003659f, 0.758407f, 0.639342f, 0.126678f}}, -}; +void BoneAnimator::LoadDefaultSkeletonByHand(vr::VRBoneTransform_t* skeleton, const bool rightHand) { + for (int i = 0; i < 31; i++) { + Transform transform = modelManager_->GetTransformByBoneIndex((HandSkeletonBone)i); + skeleton[i].orientation.w = transform.rotation[0]; + skeleton[i].orientation.x = transform.rotation[1]; + skeleton[i].orientation.y = transform.rotation[2]; + skeleton[i].orientation.z = transform.rotation[3]; + + skeleton[i].position.v[0] = transform.translation[0]; + skeleton[i].position.v[1] = transform.translation[1]; + skeleton[i].position.v[2] = transform.translation[2]; + skeleton[i].position.v[3] = 1.0f; -vr::VRBoneTransform_t leftOpenPose[NUM_BONES] = { - {{0.000000f, 0.000000f, 0.000000f, 1.000000f}, {1.000000f, -0.000000f, -0.000000f, 0.000000f}}, - {{-0.034038f, 0.036503f, 0.164722f, 1.000000f}, {-0.055147f, -0.078608f, -0.920279f, 0.379296f}}, - {{-0.012083f, 0.028070f, 0.025050f, 1.000000f}, {0.464112f, 0.567418f, 0.272106f, 0.623374f}}, - {{0.040406f, 0.000000f, -0.000000f, 1.000000f}, {0.994838f, 0.082939f, 0.019454f, 0.055130f}}, - {{0.032517f, 0.000000f, 0.000000f, 1.000000f}, {0.974793f, -0.003213f, 0.021867f, -0.222015f}}, - {{0.030464f, -0.000000f, -0.000000f, 1.000000f}, {1.000000f, -0.000000f, -0.000000f, 0.000000f}}, - {{0.000632f, 0.026866f, 0.015002f, 1.000000f}, {0.644251f, 0.421979f, -0.478202f, 0.422133f}}, - {{0.074204f, -0.005002f, 0.000234f, 1.000000f}, {0.995332f, 0.007007f, -0.039124f, 0.087949f}}, - {{0.043930f, -0.000000f, -0.000000f, 1.000000f}, {0.997891f, 0.045808f, 0.002142f, -0.045943f}}, - {{0.028695f, 0.000000f, 0.000000f, 1.000000f}, {0.999649f, 0.001850f, -0.022782f, -0.013409f}}, - {{0.022821f, 0.000000f, -0.000000f, 1.000000f}, {1.000000f, -0.000000f, 0.000000f, -0.000000f}}, - {{0.002177f, 0.007120f, 0.016319f, 1.000000f}, {0.546723f, 0.541276f, -0.442520f, 0.460749f}}, - {{0.070953f, 0.000779f, 0.000997f, 1.000000f}, {0.980294f, -0.167261f, -0.078959f, 0.069368f}}, - {{0.043108f, 0.000000f, 0.000000f, 1.000000f}, {0.997947f, 0.018493f, 0.013192f, 0.059886f}}, - {{0.033266f, 0.000000f, 0.000000f, 1.000000f}, {0.997394f, -0.003328f, -0.028225f, -0.066315f}}, - {{0.025892f, -0.000000f, 0.000000f, 1.000000f}, {0.999195f, -0.000000f, 0.000000f, 0.040126f}}, - {{0.000513f, -0.006545f, 0.016348f, 1.000000f}, {0.516692f, 0.550143f, -0.495548f, 0.429888f}}, - {{0.065876f, 0.001786f, 0.000693f, 1.000000f}, {0.990420f, -0.058696f, -0.101820f, 0.072495f}}, - {{0.040697f, 0.000000f, 0.000000f, 1.000000f}, {0.999545f, -0.002240f, 0.000004f, 0.030081f}}, - {{0.028747f, -0.000000f, -0.000000f, 1.000000f}, {0.999102f, -0.000721f, -0.012693f, 0.040420f}}, - {{0.022430f, -0.000000f, 0.000000f, 1.000000f}, {1.000000f, 0.000000f, 0.000000f, 0.000000f}}, - {{-0.002478f, -0.018981f, 0.015214f, 1.000000f}, {0.526918f, 0.523940f, -0.584025f, 0.326740f}}, - {{0.062878f, 0.002844f, 0.000332f, 1.000000f}, {0.986609f, -0.059615f, -0.135163f, 0.069132f}}, - {{0.030220f, 0.000000f, 0.000000f, 1.000000f}, {0.994317f, 0.001896f, -0.000132f, 0.106446f}}, - {{0.018187f, 0.000000f, 0.000000f, 1.000000f}, {0.995931f, -0.002010f, -0.052079f, -0.073526f}}, - {{0.018018f, 0.000000f, -0.000000f, 1.000000f}, {1.000000f, 0.000000f, 0.000000f, 0.000000f}}, - {{-0.006059f, 0.056285f, 0.060064f, 1.000000f}, {0.737238f, 0.202745f, 0.594267f, 0.249441f}}, - {{-0.040416f, -0.043018f, 0.019345f, 1.000000f}, {-0.290331f, 0.623527f, -0.663809f, -0.293734f}}, - {{-0.039354f, -0.075674f, 0.047048f, 1.000000f}, {-0.187047f, 0.678062f, -0.659285f, -0.265683f}}, - {{-0.038340f, -0.090987f, 0.082579f, 1.000000f}, {-0.183037f, 0.736793f, -0.634757f, -0.143936f}}, - {{-0.031806f, -0.087214f, 0.121015f, 1.000000f}, {-0.003659f, 0.758407f, -0.639342f, -0.126678f}}, -}; \ No newline at end of file + if (!rightHand) TransformLeftBone(skeleton[i], (HandSkeletonBone)i); + } +} \ No newline at end of file diff --git a/src/Communication/SerialCommunicationManager.cpp b/src/Communication/SerialCommunicationManager.cpp index 68b4bd26..090fad04 100644 --- a/src/Communication/SerialCommunicationManager.cpp +++ b/src/Communication/SerialCommunicationManager.cpp @@ -16,6 +16,25 @@ bool SerialCommunicationManager::IsConnected() { return isConnected_; }; +bool SerialCommunicationManager::SetCommunicationTimeout( + unsigned long ReadIntervalTimeout, + unsigned long ReadTotalTimeoutMultiplier, + unsigned long ReadTotalTimeoutConstant, + unsigned long WriteTotalTimeoutMultiplier, + unsigned long WriteTotalTimeoutConstant) { + COMMTIMEOUTS timeout; + + timeout.ReadIntervalTimeout = ReadIntervalTimeout; + timeout.ReadTotalTimeoutConstant = ReadTotalTimeoutConstant; + timeout.ReadTotalTimeoutMultiplier = ReadTotalTimeoutMultiplier; + timeout.WriteTotalTimeoutConstant = WriteTotalTimeoutConstant; + timeout.WriteTotalTimeoutMultiplier = WriteTotalTimeoutMultiplier; + + if (!SetCommTimeouts(hSerial_, &timeout)) return false; + + return true; +} + bool SerialCommunicationManager::Connect() { LogMessage("Attempting connection to device"); // We're not yet connected @@ -48,6 +67,16 @@ bool SerialCommunicationManager::Connect() { return false; } + if (!SetCommunicationTimeout(50, 0, 0, 50, 0)) { + LogError("Failed to set communication timeout"); + return false; + } + + if (!SetupComm(hSerial_, 200, 200)) { + LogError("Failed to setup comm"); + return false; + } + // If everything went fine we're connected isConnected_ = true; @@ -101,7 +130,9 @@ bool SerialCommunicationManager::ReceiveNextPacket(std::string& buff) { // will become saturated and block future reads. We've got the data we need so purge // anything else left in the buffer. There should be more data ready for us in the // buffer by the next time we poll for it. - PurgeBuffer(); + // TODO: This is currently causing lag on ESP32's so purging has been removed for now. + // Things to try in the future are purging on a time increment, or shrinking the buffer size. + // PurgeBuffer(); return true; } diff --git a/src/ControllerPose.cpp b/src/ControllerPose.cpp index 3d58ff84..2595424f 100644 --- a/src/ControllerPose.cpp +++ b/src/ControllerPose.cpp @@ -79,7 +79,7 @@ vr::DriverPose_t ControllerPose::UpdatePose() const { angularVelocityWorld.v[1] /= 100.0; angularVelocityWorld.v[2] /= 100.0; - vr::HmdQuaternion_t qAngularVelocityWorld = EulerToQuaternion(angularVelocityWorld.v[2], angularVelocityWorld.v[1], angularVelocityWorld.v[0]); + vr::HmdQuaternion_t qAngularVelocityWorld = EulerToQuaternion(static_cast(angularVelocityWorld.v[2]), static_cast(angularVelocityWorld.v[1]), static_cast(angularVelocityWorld.v[0])); vr::HmdQuaternion_t qAngularVelocityObject = MultiplyQuaternion(MultiplyQuaternion(QuatConjugate(newPose.qRotation), qAngularVelocityWorld), newPose.qRotation); diff --git a/src/DeviceDriver/DeviceDriver.cpp b/src/DeviceDriver/DeviceDriver.cpp index 39a2e478..798264db 100644 --- a/src/DeviceDriver/DeviceDriver.cpp +++ b/src/DeviceDriver/DeviceDriver.cpp @@ -17,9 +17,8 @@ DeviceDriver::DeviceDriver( handTransforms_(), hasActivated_(false), driverId_(vr::k_unTrackedDeviceIndexInvalid) { - // copy a default bone transform to our hand transform for use in finger positioning later - std::copy( - std::begin(IsRightHand() ? rightOpenPose : leftOpenPose), std::end(IsRightHand() ? rightOpenPose : leftOpenPose), std::begin(handTransforms_)); + // Load in a default skeleton + boneAnimator_->LoadDefaultSkeletonByHand(handTransforms_, configuration_.role == vr::ETrackedControllerRole::TrackedControllerRole_RightHand); } vr::EVRInitError DeviceDriver::Activate(uint32_t unObjectId) { @@ -36,7 +35,7 @@ vr::EVRInitError DeviceDriver::Activate(uint32_t unObjectId) { IsRightHand() ? "/input/skeleton/right" : "/input/skeleton/left", IsRightHand() ? "/skeleton/hand/right" : "/skeleton/hand/left", "/pose/raw", - vr::VRSkeletalTracking_Partial, + vr::EVRSkeletalTrackingLevel::VRSkeletalTracking_Full, handTransforms_, NUM_BONES, &skeletalComponentHandle_); @@ -52,11 +51,13 @@ vr::EVRInitError DeviceDriver::Activate(uint32_t unObjectId) { } void DeviceDriver::Deactivate() { - if (hasActivated_) { + if (hasActivated_.exchange(false)) { StoppingDevice(); communicationManager_->Disconnect(); driverId_ = vr::k_unTrackedDeviceIndexInvalid; hasActivated_ = false; + + poseUpdateThread_.join(); } } @@ -84,12 +85,19 @@ bool DeviceDriver::IsActive() { return hasActivated_; } -void DeviceDriver::RunFrame() { - if (hasActivated_) { - vr::VRServerDriverHost()->TrackedDevicePoseUpdated(driverId_, controllerPose_->UpdatePose(), sizeof(vr::DriverPose_t)); +void DeviceDriver::PoseUpdateThread() { + while (hasActivated_) { + vr::DriverPose_t pose = controllerPose_->UpdatePose(); + vr::VRServerDriverHost()->TrackedDevicePoseUpdated(driverId_, pose, sizeof(vr::DriverPose_t)); + + std::this_thread::sleep_for(std::chrono::milliseconds(2)); } + + DriverLog("Closing pose thread..."); } +void DeviceDriver::RunFrame() {} + bool DeviceDriver::IsRightHand() const { return configuration_.role == vr::TrackedControllerRole_RightHand; } @@ -97,14 +105,12 @@ bool DeviceDriver::IsRightHand() const { void DeviceDriver::StartDevice() { StartingDevice(); - vr::VRDriverInput()->UpdateSkeletonComponent( - skeletalComponentHandle_, vr::VRSkeletalMotionRange_WithoutController, IsRightHand() ? rightOpenPose : leftOpenPose, NUM_BONES); - vr::VRDriverInput()->UpdateSkeletonComponent( - skeletalComponentHandle_, vr::VRSkeletalMotionRange_WithController, IsRightHand() ? rightOpenPose : leftOpenPose, NUM_BONES); + vr::VRDriverInput()->UpdateSkeletonComponent(skeletalComponentHandle_, vr::VRSkeletalMotionRange_WithoutController, handTransforms_, NUM_BONES); + vr::VRDriverInput()->UpdateSkeletonComponent(skeletalComponentHandle_, vr::VRSkeletalMotionRange_WithController, handTransforms_, NUM_BONES); communicationManager_->BeginListener([&](VRInputData data) { try { - boneAnimator_->ComputeSkeletonTransforms(handTransforms_, data.flexion, IsRightHand()); + boneAnimator_->ComputeSkeletonTransforms(handTransforms_, data, IsRightHand()); vr::VRDriverInput()->UpdateSkeletonComponent(skeletalComponentHandle_, vr::VRSkeletalMotionRange_WithoutController, handTransforms_, NUM_BONES); vr::VRDriverInput()->UpdateSkeletonComponent(skeletalComponentHandle_, vr::VRSkeletalMotionRange_WithController, handTransforms_, NUM_BONES); @@ -122,4 +128,6 @@ void DeviceDriver::StartDevice() { DebugDriverLog("Exception caught while parsing comm data"); } }); + + poseUpdateThread_ = std::thread(&DeviceDriver::PoseUpdateThread, this); } diff --git a/src/DeviceDriver/KnuckleDriver.cpp b/src/DeviceDriver/KnuckleDriver.cpp index 4c637e8b..d657b7c8 100644 --- a/src/DeviceDriver/KnuckleDriver.cpp +++ b/src/DeviceDriver/KnuckleDriver.cpp @@ -10,7 +10,8 @@ KnuckleDeviceDriver::KnuckleDeviceDriver( const VRDeviceConfiguration configuration) : DeviceDriver(std::move(communicationManager), std::move(boneAnimator), std::move(serialNumber), configuration), inputComponentHandles_(), - haptic_(), approximateThumb_(approximateThumb){} + haptic_(), + approximateThumb_(approximateThumb) {} void KnuckleDeviceDriver::HandleInput(const VRInputData data) { // clang-format off @@ -21,10 +22,10 @@ void KnuckleDeviceDriver::HandleInput(const VRInputData data) { vr::VRDriverInput()->UpdateBooleanComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::ThumbstickTouch)], data.joyButton, 0); vr::VRDriverInput()->UpdateBooleanComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::TriggerClick)], data.trgButton, 0); - vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::TriggerValue)], data.flexion[1], 0); + vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::TriggerValue)], boneAnimator_->GetAverageCurlValue(data.flexion[1]), 0); vr::VRDriverInput()->UpdateBooleanComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::AClick)], data.aButton, 0); - vr::VRDriverInput()->UpdateBooleanComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::ATouch)], data.aButton || (approximateThumb_ && data.flexion[0] > 0.6), 0); //Thumb approximation + vr::VRDriverInput()->UpdateBooleanComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::ATouch)], data.aButton || (approximateThumb_ && boneAnimator_->GetAverageCurlValue(data.flexion[0]) > 0.6), 0); //Thumb approximation vr::VRDriverInput()->UpdateBooleanComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::BClick)], data.bButton, 0); vr::VRDriverInput()->UpdateBooleanComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::BTouch)], data.bButton, 0); @@ -37,10 +38,10 @@ void KnuckleDeviceDriver::HandleInput(const VRInputData data) { // We don't have a thumb on the index // vr::VRDriverInput()->UpdateScalarComponent(_inputComponentHandles[(int)KnuckleDeviceComponentIndex::THUMB], data.flexion[0], 0); - vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::FingerIndex)], data.flexion[1], 0); - vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::FingerMiddle)], data.flexion[2], 0); - vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::FingerRing)], data.flexion[3], 0); - vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::FingerPinky)], data.flexion[4], 0); + vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::FingerIndex)], boneAnimator_->GetAverageCurlValue(data.flexion[1]), 0); + vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::FingerMiddle)], boneAnimator_->GetAverageCurlValue(data.flexion[2]), 0); + vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::FingerRing)], boneAnimator_->GetAverageCurlValue(data.flexion[3]), 0); + vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(KnuckleDeviceComponentIndex::FingerPinky)], boneAnimator_->GetAverageCurlValue(data.flexion[4]), 0); // clang-format on } diff --git a/src/DeviceDriver/LucidGloveDriver.cpp b/src/DeviceDriver/LucidGloveDriver.cpp index fb77d97e..657a8155 100644 --- a/src/DeviceDriver/LucidGloveDriver.cpp +++ b/src/DeviceDriver/LucidGloveDriver.cpp @@ -25,11 +25,11 @@ void LucidGloveDeviceDriver::HandleInput(const VRInputData data) { vr::VRDriverInput()->UpdateBooleanComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::GesGrab)], data.grab, 0); vr::VRDriverInput()->UpdateBooleanComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::GesPinch)], data.pinch, 0); - vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::TrgThumb)], data.flexion[0], 0); - vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::TrgIndex)], data.flexion[1], 0); - vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::TrgMiddle)], data.flexion[2], 0); - vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::TrgRing)], data.flexion[3], 0); - vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::TrgPinky)], data.flexion[4], 0); + vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::TrgThumb)], boneAnimator_->GetAverageCurlValue(data.flexion[0]), 0); + vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::TrgIndex)], boneAnimator_->GetAverageCurlValue(data.flexion[1]), 0); + vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::TrgMiddle)], boneAnimator_->GetAverageCurlValue(data.flexion[2]), 0); + vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::TrgRing)], boneAnimator_->GetAverageCurlValue(data.flexion[3]), 0); + vr::VRDriverInput()->UpdateScalarComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::TrgPinky)], boneAnimator_->GetAverageCurlValue(data.flexion[4]), 0); vr::VRDriverInput()->UpdateBooleanComponent(inputComponentHandles_[static_cast(LucidGloveDeviceComponentIndex::BtnMenu)], data.menu, 0); // clang-format on diff --git a/src/DeviceProvider.cpp b/src/DeviceProvider.cpp index 4084d62f..76dfc12d 100644 --- a/src/DeviceProvider.cpp +++ b/src/DeviceProvider.cpp @@ -15,6 +15,11 @@ #include "Util/Quaternion.h" #include "Util/Windows.h" +#ifndef GIT_COMMIT_HASH +#define GIT_COMMIT_HASH "?" +#endif + + vr::EVRInitError DeviceProvider::Init(vr::IVRDriverContext* pDriverContext) { if (const vr::EVRInitError initError = InitServerDriverContext(pDriverContext); initError != vr::EVRInitError::VRInitError_None) return initError; @@ -22,11 +27,14 @@ vr::EVRInitError DeviceProvider::Init(vr::IVRDriverContext* pDriverContext) { InitDriverLog(vr::VRDriverLog()); //this won't print if running in release - DebugDriverLog("OpenGlove is running in DEBUG mode"); + DebugDriverLog("OpenGloves is running in DEBUG mode"); const std::string driverPath = GetDriverPath(); DriverLog("Path to DLL: %s", driverPath.c_str()); + const std::string commitHash = GIT_COMMIT_HASH; + DriverLog("Built from: %s", commitHash.substr(0,10).c_str()); + // Create background process for the overlay (used for finding controllers to bind to for tracking) if (!CreateBackgroundProcess(driverPath + R"(\bin\win64\openglove_overlay.exe)")) { DriverLog("Could not create background process: %c", GetLastErrorAsString().c_str()); @@ -77,7 +85,7 @@ std::unique_ptr DeviceProvider::InstantiateDeviceDriver( switch (configuration.communicationProtocol) { case VRCommunicationProtocol::NamedPipe: { DriverLog("Communication set to Named Pipe"); - const std::string path = R"(\\.\pipe\vrapplication\input\)" + std::string(isRightHand ? "right" : "left"); + const std::string path = R"(\\.\pipe\vrapplication\input\glove\v1\)" + std::string(isRightHand ? "right" : "left"); VRNamedPipeInputConfiguration namedPipeConfiguration(path); communicationManager = std::make_unique(namedPipeConfiguration, configuration); break; diff --git a/src/Encode/AlphaEncodingManager.cpp b/src/Encode/AlphaEncodingManager.cpp index d6c5b945..f00d730d 100644 --- a/src/Encode/AlphaEncodingManager.cpp +++ b/src/Encode/AlphaEncodingManager.cpp @@ -1,111 +1,238 @@ #include +#include +#include #include -/* Alpha encoding uses the wasted data in the delimiter from legacy to allow for optional arguments and redundancy over smaller packets */ -enum class VRCommDataAlphaEncodingCharacter : char { - FinThumb = 'A', - FinIndex = 'B', - FinMiddle = 'C', - FinRing = 'D', - FinPinky = 'E', - JoyX = 'F', - JoyY = 'G', - JoyBtn = 'H', - BtnTrg = 'I', - BtnA = 'J', - BtnB = 'K', - GesGrab = 'L', - GesPinch = 'M', - BtnMenu = 'N', - BtnCalib = 'O', +static enum class VRCommDataAlphaEncodingKey : int { + FinSplayThumb, + FinSplayIndex, + FinSplayMiddle, + FinSplayRing, + FinSplayPinky, + + FinJointThumb0, + FinJointThumb1, + FinJointThumb2, + FinJointThumb3, // unused in input but used for parity to other fingers in the array + FinJointIndex0, + FinJointIndex1, + FinJointIndex2, + FinJointIndex3, + FinJointMiddle0, + FinJointMiddle1, + FinJointMiddle2, + FinJointMiddle3, + FinJointRing0, + FinJointRing1, + FinJointRing2, + FinJointRing3, + FinJointPinky0, + FinJointPinky1, + FinJointPinky2, + FinJointPinky3, + + FinThumb, + FinIndex, + FinMiddle, + FinRing, + FinPinky, + + JoyX, + JoyY, + JoyBtn, + + BtnTrg, + BtnA, + BtnB, + + GesGrab, + GesPinch, + + BtnMenu, + BtnCalib, + + Null, }; -constexpr char VRCommDataAlphaEncodingCharacters[] = { - static_cast(VRCommDataAlphaEncodingCharacter::FinThumb), - static_cast(VRCommDataAlphaEncodingCharacter::FinIndex), - static_cast(VRCommDataAlphaEncodingCharacter::FinMiddle), - static_cast(VRCommDataAlphaEncodingCharacter::FinRing), - static_cast(VRCommDataAlphaEncodingCharacter::FinPinky), - static_cast(VRCommDataAlphaEncodingCharacter::JoyX), - static_cast(VRCommDataAlphaEncodingCharacter::JoyY), - static_cast(VRCommDataAlphaEncodingCharacter::JoyBtn), - static_cast(VRCommDataAlphaEncodingCharacter::BtnTrg), - static_cast(VRCommDataAlphaEncodingCharacter::BtnA), - static_cast(VRCommDataAlphaEncodingCharacter::BtnB), - static_cast(VRCommDataAlphaEncodingCharacter::GesGrab), - static_cast(VRCommDataAlphaEncodingCharacter::GesPinch), - static_cast(VRCommDataAlphaEncodingCharacter::BtnMenu), - static_cast(VRCommDataAlphaEncodingCharacter::BtnCalib), - static_cast(0 // Turns into a null terminated string - ) // Turns into a null terminated string +static const std::string keyCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ()"; +static bool IsCharacterKeyCharacter(const char character) { + return keyCharacters.find(character) != std::string::npos; +} + +static const std::map VRCommDataAlphaEncodingInputKeyString{ + {"A", VRCommDataAlphaEncodingKey::FinThumb}, // whole thumb curl (default curl value for thumb joints) + {"B", VRCommDataAlphaEncodingKey::FinIndex}, // whole index curl (default curl value for index joints) + {"C", VRCommDataAlphaEncodingKey::FinMiddle}, // whole middle curl (default curl value for middle joints) + {"D", VRCommDataAlphaEncodingKey::FinRing}, // whole ring curl (default curl value for ring joints) + {"E", VRCommDataAlphaEncodingKey::FinPinky}, // whole pinky curl (default curl value for pinky joints) + + {"(AAA)", VRCommDataAlphaEncodingKey::FinJointThumb0}, // thumb joint 0 + {"(AAB)", VRCommDataAlphaEncodingKey::FinJointThumb1}, // thumb joint 1 + {"(AAC)", VRCommDataAlphaEncodingKey::FinJointThumb2}, // thumb joint 2 + {"(BAA)", VRCommDataAlphaEncodingKey::FinJointIndex0}, // index joint 0 + {"(BAB)", VRCommDataAlphaEncodingKey::FinJointIndex1}, // index joint 1 + {"(BAC)", VRCommDataAlphaEncodingKey::FinJointIndex2}, // index joint 2 + {"(BAD)", VRCommDataAlphaEncodingKey::FinJointIndex3}, // index joint 3 + {"(CAA)", VRCommDataAlphaEncodingKey::FinJointMiddle0}, // middle joint 0 + {"(CAB)", VRCommDataAlphaEncodingKey::FinJointMiddle1}, // middle joint 1 + {"(CAC)", VRCommDataAlphaEncodingKey::FinJointMiddle2}, // middle joint 2 + {"(CAD)", VRCommDataAlphaEncodingKey::FinJointMiddle3}, // middle joint 3 + {"(DAA)", VRCommDataAlphaEncodingKey::FinJointRing0}, // ring joint 0 + {"(DAB)", VRCommDataAlphaEncodingKey::FinJointRing1}, // ring joint 1 + {"(DAC)", VRCommDataAlphaEncodingKey::FinJointRing2}, // ring joint 2 + {"(DAD)", VRCommDataAlphaEncodingKey::FinJointRing3}, // ring joint 3 + {"(EAA)", VRCommDataAlphaEncodingKey::FinJointPinky0}, // pinky joint 0 + {"(EAB)", VRCommDataAlphaEncodingKey::FinJointPinky1}, // pinky joint 1 + {"(EAC)", VRCommDataAlphaEncodingKey::FinJointPinky2}, // pinky joint 2 + {"(EAD)", VRCommDataAlphaEncodingKey::FinJointPinky3}, // pinky joint 3 + + {"(AB)", VRCommDataAlphaEncodingKey::FinSplayThumb}, // whole thumb splay + {"(BB)", VRCommDataAlphaEncodingKey::FinSplayIndex}, // whole index splay + {"(CB)", VRCommDataAlphaEncodingKey::FinSplayMiddle}, // whole middle splay + {"(DB)", VRCommDataAlphaEncodingKey::FinSplayRing}, // whole ring splay + {"(EB)", VRCommDataAlphaEncodingKey::FinSplayPinky}, // whole pinky splay + + {"F", VRCommDataAlphaEncodingKey::JoyX}, // joystick x component + {"G", VRCommDataAlphaEncodingKey::JoyY}, // joystick y component + {"H", VRCommDataAlphaEncodingKey::JoyBtn}, // joystick button + {"I", VRCommDataAlphaEncodingKey::BtnTrg}, // trigger button + {"J", VRCommDataAlphaEncodingKey::BtnA}, // A button + {"K", VRCommDataAlphaEncodingKey::BtnB}, // B button + {"L", VRCommDataAlphaEncodingKey::GesGrab}, // grab gesture (boolean) + {"M", VRCommDataAlphaEncodingKey::GesPinch}, // pinch gesture (boolean) + {"N", VRCommDataAlphaEncodingKey::BtnMenu}, // system button pressed (opens SteamVR menu) + {"O", VRCommDataAlphaEncodingKey::BtnCalib}, // calibration button + {"", VRCommDataAlphaEncodingKey::Null}, // Junk key }; -static std::string getArgumentSubstring(const std::string& str, const char del) { - const size_t start = str.find(del); +static const std::map VRCommDataAlphaEncodingOutputKeyString{ + {VRCommDataAlphaEncodingKey::FinThumb, "A"}, // thumb force feedback + {VRCommDataAlphaEncodingKey::FinIndex, "B"}, // index force feedback + {VRCommDataAlphaEncodingKey::FinMiddle, "C"}, // middle force feedback + {VRCommDataAlphaEncodingKey::FinRing, "D"}, // ring force feedback + {VRCommDataAlphaEncodingKey::FinPinky, "E"} // pinky force feedback +}; - if (start == std::string::npos) return std::string(); +static std::map ParseInputToMap(const std::string& str) { + std::map result; - const size_t end = - str.find_first_of(VRCommDataAlphaEncodingCharacters, start + 1); // characters may not necessarily be in order, so end at any letter + int i = 0; + while (i < str.length()) { + // Advance until we get an alphabetic character (no point in looking at values that don't have a key associated with them) - return str.substr(start + 1, end - (start + 1)); -} + if (str[i] >= 0 && str[i] <= 255 && IsCharacterKeyCharacter(str[i])) { + std::string key = {str[i]}; + i++; -static bool argValid(const std::string& str, const char del) { - return str.find(del) != std::string::npos; -} + // we're going to be parsing a "long key", i.e. (AB) for thumb finger splay. Long keys must always be enclosed in brackets + if (key[0] == '(') { + while (str[i] >= 0 && str[i] <= 255 && IsCharacterKeyCharacter(str[i]) && i < str.length()) { + key += str[i]; + i++; + } + } + + std::string value = ""; + while (str[i] >= 0 && str[i] <= 255 && isdigit(str[i]) && i < str.length()) { + value += str[i]; + i++; + } -AlphaEncodingManager::AlphaEncodingManager(const float maxAnalogValue) : EncodingManager(maxAnalogValue) {} + // Even if the value is empty we still want to use the key, it means that we have a button that is pressed (it only appears in the packet if it + // is) + if (VRCommDataAlphaEncodingInputKeyString.find(key) != VRCommDataAlphaEncodingInputKeyString.end()) + result.insert_or_assign(VRCommDataAlphaEncodingInputKeyString.at(key), value); + else + DriverLog("Unable to insert key: %s into input map as it was not found", key.c_str()); + } else + i++; + } -VRInputData AlphaEncodingManager::Decode(const std::string input) { + return result; +} + +VRInputData AlphaEncodingManager::Decode(const std::string& input) { std::array flexion = {-1.0f, -1.0f, -1.0f, -1.0f, -1.0f}; - if (argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::FinThumb))) - flexion[0] = stof(getArgumentSubstring(input, static_cast(VRCommDataAlphaEncodingCharacter::FinThumb))) / maxAnalogValue_; - if (argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::FinIndex))) - flexion[1] = stof(getArgumentSubstring(input, static_cast(VRCommDataAlphaEncodingCharacter::FinIndex))) / maxAnalogValue_; - if (argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::FinMiddle))) - flexion[2] = stof(getArgumentSubstring(input, static_cast(VRCommDataAlphaEncodingCharacter::FinMiddle))) / maxAnalogValue_; - if (argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::FinRing))) - flexion[3] = stof(getArgumentSubstring(input, static_cast(VRCommDataAlphaEncodingCharacter::FinRing))) / maxAnalogValue_; - if (argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::FinPinky))) - flexion[4] = stof(getArgumentSubstring(input, static_cast(VRCommDataAlphaEncodingCharacter::FinPinky))) / maxAnalogValue_; + std::array, 5> jointFlexion; + + std::array splay = {-2.0f, -2.0f, -2.0f, -2.0f, -2.0f}; + + // This map contains all the inputs we've got from the packet we received + std::map inputMap = ParseInputToMap(input); + + // curl is 0.0f -> 1.0f inclusive + if (inputMap.find(VRCommDataAlphaEncodingKey::FinThumb) != inputMap.end()) + flexion[0] = std::stof(inputMap.at(VRCommDataAlphaEncodingKey::FinThumb)) / maxAnalogValue_; + if (inputMap.find(VRCommDataAlphaEncodingKey::FinIndex) != inputMap.end()) + flexion[1] = std::stof(inputMap.at(VRCommDataAlphaEncodingKey::FinIndex)) / maxAnalogValue_; + if (inputMap.find(VRCommDataAlphaEncodingKey::FinMiddle) != inputMap.end()) + flexion[2] = std::stof(inputMap.at(VRCommDataAlphaEncodingKey::FinMiddle)) / maxAnalogValue_; + if (inputMap.find(VRCommDataAlphaEncodingKey::FinRing) != inputMap.end()) + flexion[3] = std::stof(inputMap.at(VRCommDataAlphaEncodingKey::FinRing)) / maxAnalogValue_; + if (inputMap.find(VRCommDataAlphaEncodingKey::FinPinky) != inputMap.end()) + flexion[4] = std::stof(inputMap.at(VRCommDataAlphaEncodingKey::FinPinky)) / maxAnalogValue_; + + // fill all the joints + int curJoint = (int)VRCommDataAlphaEncodingKey::FinJointThumb0; + for (int i = 0; i < 5; i++) { + for (int k = 0; k < 4; k++) { + VRCommDataAlphaEncodingKey joint = static_cast(curJoint); + jointFlexion[i][k] = inputMap.find(joint) != inputMap.end() ? (std::stof(inputMap.at(joint)) / maxAnalogValue_) : flexion[i]; + curJoint++; + } + } + + // splay is -1.0f -> 1.0f inclusive + if (inputMap.find(VRCommDataAlphaEncodingKey::FinSplayThumb) != inputMap.end()) + splay[0] = (std::stof(inputMap.at(VRCommDataAlphaEncodingKey::FinSplayThumb)) / maxAnalogValue_ - 0.5f) * 2.0f; + if (inputMap.find(VRCommDataAlphaEncodingKey::FinSplayIndex) != inputMap.end()) + splay[1] = (std::stof(inputMap.at(VRCommDataAlphaEncodingKey::FinSplayIndex)) / maxAnalogValue_ - 0.5f) * 2.0f; + if (inputMap.find(VRCommDataAlphaEncodingKey::FinSplayMiddle) != inputMap.end()) + splay[2] = (std::stof(inputMap.at(VRCommDataAlphaEncodingKey::FinSplayMiddle)) / maxAnalogValue_ - 0.5f) * 2.0f; + if (inputMap.find(VRCommDataAlphaEncodingKey::FinSplayRing) != inputMap.end()) + splay[3] = (std::stof(inputMap.at(VRCommDataAlphaEncodingKey::FinSplayRing)) / maxAnalogValue_ - 0.5f) * 2.0f; + if (inputMap.find(VRCommDataAlphaEncodingKey::FinSplayPinky) != inputMap.end()) + splay[4] = (std::stof(inputMap.at(VRCommDataAlphaEncodingKey::FinSplayPinky)) / maxAnalogValue_ - 0.5f) * 2.0f; float joyX = 0; float joyY = 0; - if (argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::JoyX))) - joyX = 2 * stof(getArgumentSubstring(input, static_cast(VRCommDataAlphaEncodingCharacter::JoyX))) / maxAnalogValue_ - 1; - if (argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::JoyY))) - joyY = 2 * stof(getArgumentSubstring(input, static_cast(VRCommDataAlphaEncodingCharacter::JoyY))) / maxAnalogValue_ - 1; + + // joystick axis are -1.0f -> 1.0f inclusive + if (inputMap.find(VRCommDataAlphaEncodingKey::JoyX) != inputMap.end()) + joyX = 2 * std::stof(inputMap.at(VRCommDataAlphaEncodingKey::JoyX)) / maxAnalogValue_ - 1; + if (inputMap.find(VRCommDataAlphaEncodingKey::JoyY) != inputMap.end()) + joyY = 2 * std::stof(inputMap.at(VRCommDataAlphaEncodingKey::JoyY)) / maxAnalogValue_ - 1; VRInputData inputData( - flexion, + jointFlexion, + splay, joyX, joyY, - argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::JoyBtn)), - argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::BtnTrg)), - argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::BtnA)), - argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::BtnB)), - argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::GesGrab)), - argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::GesPinch)), - argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::BtnMenu)), - argValid(input, static_cast(VRCommDataAlphaEncodingCharacter::BtnCalib))); - + inputMap.find(VRCommDataAlphaEncodingKey::JoyBtn) != inputMap.end(), + inputMap.find(VRCommDataAlphaEncodingKey::BtnTrg) != inputMap.end(), + inputMap.find(VRCommDataAlphaEncodingKey::BtnA) != inputMap.end(), + inputMap.find(VRCommDataAlphaEncodingKey::BtnB) != inputMap.end(), + inputMap.find(VRCommDataAlphaEncodingKey::GesGrab) != inputMap.end(), + inputMap.find(VRCommDataAlphaEncodingKey::GesPinch) != inputMap.end(), + inputMap.find(VRCommDataAlphaEncodingKey::BtnMenu) != inputMap.end(), + inputMap.find(VRCommDataAlphaEncodingKey::BtnCalib) != inputMap.end()); return inputData; } std::string AlphaEncodingManager::Encode(const VRFFBData& input) { std::string result = StringFormat( - "%c%d%c%d%c%d%c%d%c%d\n", - static_cast(VRCommDataAlphaEncodingCharacter::FinThumb), + "%s%d%s%d%s%d%s%d%s%d\n", + VRCommDataAlphaEncodingOutputKeyString.at(VRCommDataAlphaEncodingKey::FinThumb).c_str(), input.thumbCurl, - static_cast(VRCommDataAlphaEncodingCharacter::FinIndex), + VRCommDataAlphaEncodingOutputKeyString.at(VRCommDataAlphaEncodingKey::FinIndex).c_str(), input.indexCurl, - static_cast(VRCommDataAlphaEncodingCharacter::FinMiddle), + VRCommDataAlphaEncodingOutputKeyString.at(VRCommDataAlphaEncodingKey::FinMiddle).c_str(), input.middleCurl, - static_cast(VRCommDataAlphaEncodingCharacter::FinRing), + VRCommDataAlphaEncodingOutputKeyString.at(VRCommDataAlphaEncodingKey::FinRing).c_str(), input.ringCurl, - static_cast(VRCommDataAlphaEncodingCharacter::FinPinky), + VRCommDataAlphaEncodingOutputKeyString.at(VRCommDataAlphaEncodingKey::FinPinky).c_str(), input.pinkyCurl); + return result; } \ No newline at end of file diff --git a/src/Encode/EncodingManager.cpp b/src/Encode/EncodingManager.cpp deleted file mode 100644 index bafe31e6..00000000 --- a/src/Encode/EncodingManager.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "Encode/EncodingManager.h" - -VRFFBData::VRFFBData() : VRFFBData(0, 0, 0, 0, 0) {} - -VRFFBData::VRFFBData(const short thumbCurl, const short indexCurl, const short middleCurl, const short ringCurl, const short pinkyCurl) - : thumbCurl(thumbCurl), indexCurl(indexCurl), middleCurl(middleCurl), ringCurl(ringCurl), pinkyCurl(pinkyCurl) {} - -VRInputData::VRInputData() : VRInputData({0, 0, 0, 0, 0}, 0.0f, 0.0f, false, false, false, false, false, false, false, false) {} - -VRInputData::VRInputData( - const std::array flexion, - const float joyX, - const float joyY, - const bool joyButton, - const bool trgButton, - const bool aButton, - const bool bButton, - const bool grab, - const bool pinch, - const bool menu, - const bool calibrate) - : flexion(flexion), - joyX(joyX), - joyY(joyY), - joyButton(joyButton), - trgButton(trgButton), - aButton(aButton), - bButton(bButton), - grab(grab), - pinch(pinch), - menu(menu), - calibrate(calibrate) {} - -EncodingManager::EncodingManager(const float maxAnalogValue) : maxAnalogValue_(maxAnalogValue) {} diff --git a/src/Encode/LegacyEncodingManager.cpp b/src/Encode/LegacyEncodingManager.cpp index b54ecd1b..4fe3db7f 100644 --- a/src/Encode/LegacyEncodingManager.cpp +++ b/src/Encode/LegacyEncodingManager.cpp @@ -22,7 +22,7 @@ enum class VRCommDataLegacyEncodingPosition : int { LegacyEncodingManager::LegacyEncodingManager(const float maxAnalogValue) : EncodingManager(maxAnalogValue) {} -VRInputData LegacyEncodingManager::Decode(const std::string input) { +VRInputData LegacyEncodingManager::Decode(const std::string& input) { std::string buf; std::stringstream ss(input); diff --git a/src/Util/AnimLoader.cpp b/src/Util/AnimLoader.cpp new file mode 100644 index 00000000..abfd3960 --- /dev/null +++ b/src/Util/AnimLoader.cpp @@ -0,0 +1,217 @@ +#include "Util/AnimLoader.h" + +#include "DriverLog.h" + +#define TINYGLTF_IMPLEMENTATION +#define TINYGLTF_NO_STB_IMAGE +#define TINYGLTF_NO_STB_IMAGE_WRITE +#include "tiny_gltf.h" + +static float Lerp(const float& a, const float& b, const float& f) { + return a + f * (b - a); +} + +static const std::array emptyRotation = {0.0f, 0.0f, 0.0f, 0.0f}; +static const std::array emptyTranslation = {0.0f, 0.0f, 0.0f}; + +Transform::Transform() : rotation(emptyRotation), translation(emptyTranslation) {} +AnimationData::AnimationData() : startTime(0.0f), endTime(0.0f) {} + +static const std::map GLTFNodeBoneMap{ + {"REF:Root", HandSkeletonBone::Root}, + {"REF:wrist_r", HandSkeletonBone::Wrist}, + {"REF:finger_thumb_0_r", HandSkeletonBone::Thumb0}, + {"REF:finger_thumb_1_r", HandSkeletonBone::Thumb1}, + {"REF:finger_thumb_2_r", HandSkeletonBone::Thumb2}, + {"REF:finger_thumb_r_end", HandSkeletonBone::Thumb3}, + {"REF:finger_index_meta_r", HandSkeletonBone::IndexFinger0}, + {"REF:finger_index_0_r", HandSkeletonBone::IndexFinger1}, + {"REF:finger_index_1_r", HandSkeletonBone::IndexFinger2}, + {"REF:finger_index_2_r", HandSkeletonBone::IndexFinger3}, + {"REF:finger_index_r_end", HandSkeletonBone::IndexFinger4}, + {"REF:finger_middle_meta_r", HandSkeletonBone::MiddleFinger0}, + {"REF:finger_middle_0_r", HandSkeletonBone::MiddleFinger1}, + {"REF:finger_middle_1_r", HandSkeletonBone::MiddleFinger2}, + {"REF:finger_middle_2_r", HandSkeletonBone::MiddleFinger3}, + {"REF:finger_middle_r_end", HandSkeletonBone::MiddleFinger4}, + {"REF:finger_ring_meta_r", HandSkeletonBone::RingFinger0}, + {"REF:finger_ring_0_r", HandSkeletonBone::RingFinger1}, + {"REF:finger_ring_1_r", HandSkeletonBone::RingFinger2}, + {"REF:finger_ring_2_r", HandSkeletonBone::RingFinger3}, + {"REF:finger_ring_r_end", HandSkeletonBone::RingFinger4}, + {"REF:finger_pinky_meta_r", HandSkeletonBone::PinkyFinger0}, + {"REF:finger_pinky_0_r", HandSkeletonBone::PinkyFinger1}, + {"REF:finger_pinky_1_r", HandSkeletonBone::PinkyFinger2}, + {"REF:finger_pinky_2_r", HandSkeletonBone::PinkyFinger3}, + {"REF:finger_pinky_r_end", HandSkeletonBone::PinkyFinger4}, + {"REF:finger_thumb_r_aux", HandSkeletonBone::AuxThumb}, + {"REF:finger_index_r_aux", HandSkeletonBone::AuxIndexFinger}, + {"REF:finger_middle_r_aux", HandSkeletonBone::AuxMiddleFinger}, + {"REF:finger_ring_r_aux", HandSkeletonBone::AuxRingFinger}, + {"REF:finger_pinky_r_aux", HandSkeletonBone::AuxPinkyFinger}}; + +static void MapRightTransform(Transform& transform, const HandSkeletonBone& boneIndex) { + std::array quat = transform.rotation; + switch (boneIndex) { + case HandSkeletonBone::Root: { + return; + } + + case HandSkeletonBone::IndexFinger0: { + transform.rotation[0] *= -1; + transform.rotation[1] *= -1; + transform.rotation[2] *= -1; + transform.rotation[3] *= -1; + break; + } + case HandSkeletonBone::AuxThumb: + quat[0] *= -1; + quat[1] *= -1; + quat[2] *= -1; + quat[3] *= -1; + case HandSkeletonBone::AuxIndexFinger: + case HandSkeletonBone::AuxMiddleFinger: + case HandSkeletonBone::AuxRingFinger: + case HandSkeletonBone::AuxPinkyFinger: + case HandSkeletonBone::Wrist: { + transform.rotation[0] = -quat[2]; + transform.rotation[1] = quat[3]; + transform.rotation[2] = quat[0]; + transform.rotation[3] = -quat[1]; + + transform.translation[0] *= -1; + transform.translation[2] *= -1; + + break; + } + } +} + +// convert from xyzw (gltf format) to wxyz (openvr format) +static void MapRotation(std::array& rotation) { + float temp0 = rotation[0]; + rotation[0] = rotation[3]; + rotation[3] = rotation[2]; + rotation[2] = rotation[1]; + rotation[1] = temp0; +} + +bool GLTFModelManager::Load() { + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + + const bool ret = loader.LoadBinaryFromFile(&model_, &err, &warn, fileName_); + + if (!warn.empty()) { + DriverLog("Warning parsing gltf file: %s", warn.c_str()); + return false; + } + + if (!err.empty()) { + DriverLog("Error parsing gltf file: %s", err.c_str()); + return false; + } + + if (!ret) { + DriverLog("Failed to parse gltf"); + return false; + } + + initialTransforms_ = std::vector(GLTFNodeBoneMap.size()); + keyframeTransforms_ = std::vector>(GLTFNodeBoneMap.size()); + + LoadKeyframeTimes(); + LoadInitialTransforms(); + + return true; +} + +void GLTFModelManager::LoadKeyframeTimes() { + const tinygltf::Accessor accessor = model_.accessors[0]; + keyframeTimes_.resize(accessor.count); + + const tinygltf::BufferView bufferView = model_.bufferViews[accessor.bufferView]; + const std::vector& bufData = model_.buffers[0].data; + memcpy(&keyframeTimes_[0], bufData.data() + bufferView.byteOffset + accessor.byteOffset, accessor.count * sizeof(float)); +} + +void GLTFModelManager::LoadInitialTransforms() { + for (size_t nodeIndex = 0; nodeIndex < model_.nodes.size(); nodeIndex++) { + const tinygltf::Node& node = model_.nodes[nodeIndex]; + + try { + const HandSkeletonBone bone = GLTFNodeBoneMap.at(node.name); + const int boneIndex = static_cast(bone); + + Transform transform; + if (node.rotation.size() >= 4) { + transform.rotation[0] = static_cast(node.rotation[0]); + transform.rotation[1] = static_cast(node.rotation[1]); + transform.rotation[2] = static_cast(node.rotation[2]); + transform.rotation[3] = static_cast(node.rotation[3]); + MapRotation(transform.rotation); + } + if (node.translation.size() >= 3) { + transform.translation[0] = static_cast(node.translation[0]); + transform.translation[1] = static_cast(node.translation[1]); + transform.translation[2] = static_cast(node.translation[2]); + } + MapRightTransform(transform, (HandSkeletonBone)boneIndex); + + initialTransforms_[boneIndex] = transform; + + const tinygltf::Animation& animation = model_.animations[0]; + std::vector& transforms = keyframeTransforms_[boneIndex]; + + transforms.resize(keyframeTimes_.size()); + + for (auto& channel : animation.channels) { + if (channel.target_node != nodeIndex) continue; + + const tinygltf::Accessor& accessor = model_.accessors[animation.samplers[channel.sampler].output]; + switch (accessor.type) { + // rotation via quaternion + case TINYGLTF_TYPE_VEC4: { + std::vector> keyframes = GetVecN<4>(accessor); + for (size_t i = 0; i < keyframes.size(); i++) { + transforms[i].rotation = keyframes[i]; + MapRotation(transforms[i].rotation); + }; + break; + } + // translation + case TINYGLTF_TYPE_VEC3: { + std::vector> keyframes = GetVecN<3>(accessor); + for (size_t i = 0; i < keyframes.size(); i++) { + transforms[i].translation = keyframes[i]; + MapRightTransform(transforms[i], bone); + }; + break; + } + } + } + + } catch (const std::out_of_range&) { + DriverLog("Not parsing node as it was not defined as a bone: %i", nodeIndex); + continue; + } + } +} + +Transform GLTFModelManager::GetTransformByBoneIndex(const HandSkeletonBone& boneIndex) const { + return initialTransforms_[static_cast(boneIndex)]; +} + +AnimationData GLTFModelManager::GetAnimationDataByBoneIndex(const HandSkeletonBone& boneIndex, const float f) const { + const size_t lowerKeyframeIndex = std::upper_bound(keyframeTimes_.begin(), keyframeTimes_.end(), f) - keyframeTimes_.begin() - 1; + const size_t upperKeyframeIndex = lowerKeyframeIndex < keyframeTimes_.size() - 1 ? lowerKeyframeIndex + 1 : lowerKeyframeIndex; + + AnimationData result; + result.startTransform = keyframeTransforms_[static_cast(boneIndex)][lowerKeyframeIndex]; + result.startTime = keyframeTimes_[lowerKeyframeIndex]; + result.endTransform = keyframeTransforms_[static_cast(boneIndex)][upperKeyframeIndex]; + result.endTime = keyframeTimes_[upperKeyframeIndex]; + + return result; +} \ No newline at end of file diff --git a/src/Util/Quaternion.cpp b/src/Util/Quaternion.cpp index d3628e35..2dacce68 100644 --- a/src/Util/Quaternion.cpp +++ b/src/Util/Quaternion.cpp @@ -52,6 +52,7 @@ vr::HmdMatrix33_t GetRotationMatrix(const vr::HmdMatrix34_t& matrix) { return result; } + vr::HmdVector3_t MultiplyMatrix(const vr::HmdMatrix33_t& matrix, const vr::HmdVector3_t& vector) { vr::HmdVector3_t result{}; @@ -93,6 +94,17 @@ vr::HmdQuaternion_t MultiplyQuaternion(const vr::HmdQuaternion_t& q, const vr::H return result; } +vr::HmdQuaternionf_t MultiplyQuaternion(const vr::HmdQuaternionf_t& q, const vr::HmdQuaternionf_t& r) { + vr::HmdQuaternionf_t result{}; + + result.w = r.w * q.w - r.x * q.x - r.y * q.y - r.z * q.z; + result.x = r.w * q.x + r.x * q.w - r.y * q.z + r.z * q.y; + result.y = r.w * q.y + r.x * q.z + r.y * q.w - r.z * q.x; + result.z = r.w * q.z - r.x * q.y + r.y * q.x + r.z * q.w; + + return result; +} + vr::HmdQuaternion_t EulerToQuaternion(const double& yaw, const double& pitch, const double& roll) { const double cy = cos(yaw * 0.5); const double sy = sin(yaw * 0.5); @@ -109,24 +121,38 @@ vr::HmdQuaternion_t EulerToQuaternion(const double& yaw, const double& pitch, co return q; } + +vr::HmdQuaternionf_t EulerToQuaternion(const float& yaw, const float& pitch, const float& roll) { + const float cy = cos(yaw * 0.5); + const float sy = sin(yaw * 0.5); + const float cp = cos(pitch * 0.5); + const float sp = sin(pitch * 0.5); + const float cr = cos(roll * 0.5); + const float sr = sin(roll * 0.5); + + vr::HmdQuaternionf_t q{}; + q.w = cr * cp * cy + sr * sp * sy; + q.x = sr * cp * cy - cr * sp * sy; + q.y = cr * sp * cy + sr * cp * sy; + q.z = cr * cp * sy - sr * sp * cy; + + return q; +} vr::HmdVector3_t QuaternionToEuler(const vr::HmdQuaternion_t& q) { vr::HmdVector3_t result; const double unit = (q.x * q.x) + (q.y * q.y) + (q.z * q.z) + (q.w * q.w); const float test = q.x * q.w - q.y * q.z; - if (test > 0.4995f * unit) - { + if (test > 0.4995f * unit) { result.v[0] = M_PI / 2; result.v[1] = (2 * atan2(q.y / unit, q.x / unit)); result.v[2] = 0; - } else if (test < -0.4995f * unit) - { + } else if (test < -0.4995f * unit) { result.v[0] = -M_PI / 2; result.v[1] = (-2 * atan2(q.y / unit, q.x / unit)); result.v[2] = 0; - } else - { + } else { result.v[0] = asin(2 * (q.w * q.x - q.y * q.z) / unit); result.v[1] = atan2((2 / unit * q.w * q.y + 2 / unit * q.z * q.x), (1 - 2 / unit * (q.x * q.x + q.y * q.y))); result.v[2] = atan2((2 / unit * q.w * q.z + 2 / unit * q.x * q.y), (1 - 2 / unit * (q.z * q.z + q.x * q.x)));