diff --git a/demos/combustion_demo/src/combustion_demo.cpp b/demos/combustion_demo/src/combustion_demo.cpp index 4aaf9f7a6ef0fe802a4e99c67e717eaa47b429de..ab7bedcef95122ba366b7797bb0f60e947c649c9 100644 --- a/demos/combustion_demo/src/combustion_demo.cpp +++ b/demos/combustion_demo/src/combustion_demo.cpp @@ -33,6 +33,7 @@ #include "phx/core/runtime_component.hpp" #include "phx/display/display_system_openvr.hpp" #include "phx/display/display_system_window.hpp" +#include "phx/input/device_system.hpp" #include "phx/input/input_system.hpp" #include "phx/rendering/auxiliary/splash_screen.hpp" #include "phx/rendering/components/mesh_handle.hpp" @@ -47,11 +48,12 @@ #endif int main(int, char**) { - std::unique_ptr<phx::Engine> engine = phx::Setup::CreateDefaultEngine(false); + std::unique_ptr<phx::Engine> engine = phx::Setup::CreateDefaultEngine(true); auto scene = engine->GetScene(); auto rendering_system = engine->GetSystem<phx::RenderingSystem>(); rendering_system->SetEnabled(false); auto openvr_system = engine->GetSystem<phx::DisplaySystemOpenVR>(); + auto device_system = engine->GetSystem<phx::DeviceSystem>(); phx::SplashScreen* splash = engine->CreateSystem<phx::SplashScreen>( engine->GetSystem<phx::DisplaySystemWindow>()->GetWindow()); @@ -70,15 +72,17 @@ int main(int, char**) { if (key == 'q') engine->Stop(); }); - auto virtual_platform = scene->GetEntitiesWithComponents< - phx::RuntimeComponent<phx::USER_PLATFORM>>()[0]; - // virtual_platform->AddComponent<DesktopNavigationBehavior>( - // engine->GetSystem<phx::InputSystem>()); - auto controller_navigation_behavior = - virtual_platform->AddComponent<VRControllerInteractionBehavior>( - openvr_system); + auto right_controller_entities = scene->GetEntitiesWithComponents< + phx::RuntimeComponent<phx::RIGHT_CONTROLLER>>(); + VRControllerInteractionBehavior* right_interaction_behavior = nullptr; + if (right_controller_entities.size() >= 1) { + auto right_controller_entity = right_controller_entities[0]; + right_interaction_behavior = + right_controller_entity->AddComponent<VRControllerInteractionBehavior>( + device_system); + } - auto handle = std::async([&controller_navigation_behavior, &scene, + auto handle = std::async([&right_interaction_behavior, &scene, rendering_system, splash, input_system, openvr_system]() { auto model_surface_entity = phx::SceneLoader::InsertModelIntoScene( @@ -133,7 +137,8 @@ int main(int, char**) { glm::vec3(1.0f, 1.0f, 1.0f)); floor_material->GetMaterial()->SetAmbientColor(glm::vec3(0.1f, 0.1f, 0.1f)); - controller_navigation_behavior->SetTarget(vis_root_transform); + if (right_interaction_behavior) + right_interaction_behavior->SetTarget(vis_root_transform); auto desk_entity = phx::SceneLoader::InsertModelIntoScene( "models/cube/cube2.obj", scene.get()); @@ -199,6 +204,8 @@ int main(int, char**) { if (!openvr_system) { camera->AddComponent<DesktopNavigationBehavior>(input_system); + camera->GetFirstComponent<phx::Transform>()->Translate( + glm::vec3(0.0f, 1.0f, 1.0f)); } engine->Run(); diff --git a/demos/combustion_demo/src/vr_controller_interaction_behavior.cpp b/demos/combustion_demo/src/vr_controller_interaction_behavior.cpp index a7d7e2b0fe6357b0c3db50b1d45a35ab56143723..6d113a15013a3ef318979fae5e1fbf739d8451ae 100644 --- a/demos/combustion_demo/src/vr_controller_interaction_behavior.cpp +++ b/demos/combustion_demo/src/vr_controller_interaction_behavior.cpp @@ -41,47 +41,31 @@ SUPPRESS_WARNINGS_END #include "phx/rendering/components/transform.hpp" VRControllerInteractionBehavior::VRControllerInteractionBehavior( - phx::DisplaySystemOpenVR* display_system_openvr) - : display_system_openvr_(display_system_openvr) {} + phx::DeviceSystem* device_system) + : device_system_(device_system) {} -void VRControllerInteractionBehavior::OnUpdate() { - // If there exists an HMD and a transform. - phx::HMD* hmd = nullptr; - if (display_system_openvr_ != nullptr) { - hmd = display_system_openvr_->GetHMD(); - } +void VRControllerInteractionBehavior::OnButtonSignal( + phx::VRController::ButtonId id, phx::VRController::ButtonEvent event) { const auto transform = GetEntity()->GetFirstComponent<phx::Transform>(); - if (hmd == nullptr || transform == nullptr) return; - - auto indices = hmd->GetControllerIndices(); - - for (auto i = 0u; i < indices.size(); ++i) { - vr::VRControllerState_t controller_state; - vr::VRSystem()->GetControllerState(indices[i], &controller_state, - sizeof controller_state); - - if (target_) { - if (controller_state.ulButtonPressed & - vr::ButtonMaskFromId(vr::EVRButtonId::k_EButton_SteamVR_Trigger)) { - target_->SetParent( - GetEntity() - ->GetScene() - ->GetEntitiesWithComponents< - phx::RuntimeComponent<phx::RIGHT_CONTROLLER>>()[0] - ->GetFirstComponent<phx::Transform>()); - is_grabbed_ = true; - release_delay_ = 100; - } else if (is_grabbed_) { - release_delay_--; - } - if (release_delay_ == 0) { - target_->SetParent(nullptr); - is_grabbed_ = false; - } + if (target_ != nullptr) { + if (event == phx::VRController::BUTTON_PRESSED && + id == vr::EVRButtonId::k_EButton_SteamVR_Trigger) { + if (transform != nullptr) + target_->SetParent(GetEntity()->GetFirstComponent<phx::Transform>()); + } + if (event == phx::VRController::BUTTON_RELEASED && + id == vr::EVRButtonId::k_EButton_SteamVR_Trigger) { + target_->SetParent(nullptr); } } } +void VRControllerInteractionBehavior::OnUpdate() { + if (side_ == phx::VRController::INVALID_CONTROLLER) { + RegisterOnDeviceSignal(); + } +} + void VRControllerInteractionBehavior::SetTarget(phx::Transform* target) { target_ = target; } @@ -89,3 +73,33 @@ void VRControllerInteractionBehavior::SetTarget(phx::Transform* target) { phx::Transform* VRControllerInteractionBehavior::GetTarget() const { return target_; } + +void VRControllerInteractionBehavior::RegisterOnDeviceSignal() { + if (GetEntity() + ->GetFirstComponent<phx::RuntimeComponent<phx::LEFT_CONTROLLER>>() != + nullptr) + side_ = phx::VRController::LEFT_CONTROLLER; + else if (GetEntity() + ->GetFirstComponent< + phx::RuntimeComponent<phx::RIGHT_CONTROLLER>>() != nullptr) + side_ = phx::VRController::RIGHT_CONTROLLER; + else + phx::warn( + "Added VRControllerInteractionBehavior to a non-controller entity"); + + phx::VRController* controller = nullptr; + for (auto cont : device_system_->GetDevices<phx::VRController>()) { + if (cont->GetSide() == side_) { + controller = cont; + break; + } + } + + if (controller != nullptr) { + controller->RegisterButtonSignal( + [this](phx::VRController::ButtonId id, + phx::VRController::ButtonEvent event) { + this->OnButtonSignal(id, event); + }); + } +} diff --git a/demos/combustion_demo/src/vr_controller_interaction_behavior.hpp b/demos/combustion_demo/src/vr_controller_interaction_behavior.hpp index 6ed6f309f1cb452e1e330c06ea634ae5ed5c516b..c66c364edaf9e77ec3f5dd6938facee53789c175 100644 --- a/demos/combustion_demo/src/vr_controller_interaction_behavior.hpp +++ b/demos/combustion_demo/src/vr_controller_interaction_behavior.hpp @@ -23,14 +23,14 @@ #ifndef DEMOS_COMBUSTION_DEMO_SRC_VR_CONTROLLER_INTERACTION_BEHAVIOR_HPP_ #define DEMOS_COMBUSTION_DEMO_SRC_VR_CONTROLLER_INTERACTION_BEHAVIOR_HPP_ -#include "phx/display/display_system_openvr.hpp" +#include "phx/input/device_system.hpp" +#include "phx/input/vr_controller.hpp" #include "phx/rendering/components/transform.hpp" #include "phx/scripting/behavior.hpp" class VRControllerInteractionBehavior : public phx::Behavior { public: - explicit VRControllerInteractionBehavior( - phx::DisplaySystemOpenVR* display_system_openvr); + explicit VRControllerInteractionBehavior(phx::DeviceSystem* device_system); VRControllerInteractionBehavior(const VRControllerInteractionBehavior& that) = default; VRControllerInteractionBehavior(VRControllerInteractionBehavior&& temp) = @@ -42,17 +42,21 @@ class VRControllerInteractionBehavior : public phx::Behavior { VRControllerInteractionBehavior&& temp) = default; void OnUpdate() override; + void OnButtonSignal(phx::VRController::ButtonId id, + phx::VRController::ButtonEvent event); void SetTarget(phx::Transform* target); phx::Transform* GetTarget() const; protected: - phx::DisplaySystemOpenVR* display_system_openvr_; + phx::DeviceSystem* device_system_; private: + void RegisterOnDeviceSignal(); + phx::Transform* target_ = nullptr; - bool is_grabbed_ = false; - int release_delay_ = 0; + phx::VRController::ControllerSide side_ = + phx::VRController::INVALID_CONTROLLER; }; #endif // DEMOS_COMBUSTION_DEMO_SRC_VR_CONTROLLER_INTERACTION_BEHAVIOR_HPP_ diff --git a/demos/viewer/src/navigation_behavior.cpp b/demos/viewer/src/navigation_behavior.cpp deleted file mode 100644 index e9c914fd4d8658c03d65d1362203764739d3e2ed..0000000000000000000000000000000000000000 --- a/demos/viewer/src/navigation_behavior.cpp +++ /dev/null @@ -1,72 +0,0 @@ -//------------------------------------------------------------------------------ -// Project Phoenix -// -// Copyright (c) 2017-2018 RWTH Aachen University, Germany, -// Virtual Reality & Immersive Visualization Group. -//------------------------------------------------------------------------------ -// License -// -// Licensed under the 3-Clause BSD License (the "License"); -// you may not use this file except in compliance with the License. -// See the file LICENSE for the full text. -// You may obtain a copy of the License at -// -// https://opensource.org/licenses/BSD-3-Clause -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//------------------------------------------------------------------------------ - -#include "navigation_behavior.hpp" - -#include <vector> - -#include "phx/suppress_warnings.hpp" - -SUPPRESS_WARNINGS_BEGIN -#include "glm/detail/type_vec3.hpp" -#include "glm/glm.hpp" -#include "glm/gtc/matrix_access.hpp" -SUPPRESS_WARNINGS_END - -#include "phx/core/engine.hpp" -#include "phx/core/entity.hpp" -#include "phx/core/scene.hpp" -#include "phx/display/display_system_openvr.hpp" -#include "phx/display/hmd.hpp" -#include "phx/rendering/components/transform.hpp" - -NavigationBehavior::NavigationBehavior( - phx::DisplaySystemOpenVR* display_system_openvr) - : display_system_openvr_(display_system_openvr) {} - -void NavigationBehavior::OnUpdate() { - // If there exists an HMD and a transform. - phx::HMD* hmd = nullptr; - if (display_system_openvr_ != nullptr) { - hmd = display_system_openvr_->GetHMD(); - } - const auto transform = GetEntity()->GetFirstComponent<phx::Transform>(); - if (hmd == nullptr || transform == nullptr) return; - - auto indices = hmd->GetControllerIndices(); - - for (auto i = 0u; i < indices.size(); ++i) { - vr::VRControllerState_t controller_state; - vr::VRSystem()->GetControllerState(indices[i], &controller_state, - sizeof controller_state); - - // Set the transform based on whether the trigger is pressed. - if (controller_state.ulButtonTouched & - vr::ButtonMaskFromId(vr::EVRButtonId::k_EButton_SteamVR_Trigger)) - transform->Translate( - -0.05F * glm::column(hmd->GetRightControllerTransformation(), 2)); - if (controller_state.ulButtonTouched & - vr::ButtonMaskFromId(vr::EVRButtonId::k_EButton_SteamVR_Touchpad)) - transform->Translate( - 0.05F * glm::column(hmd->GetRightControllerTransformation(), 2)); - } -} diff --git a/demos/viewer/src/viewer.cpp b/demos/viewer/src/viewer.cpp index d8d6fe5a575496de37b140e615c349bb0ee62fa0..a43834aba9cadeb24c0425e9c2373a7b4026f78d 100644 --- a/demos/viewer/src/viewer.cpp +++ b/demos/viewer/src/viewer.cpp @@ -22,10 +22,7 @@ #include <chrono> #include <future> -#include <iostream> #include <memory> -#include <string> -#include <utility> #include <vector> #include "phx/core/engine.hpp" @@ -36,47 +33,40 @@ #include "phx/display/display_system_openvr.hpp" #include "phx/display/display_system_window.hpp" #include "phx/display/window.hpp" +#include "phx/input/device_system.hpp" #include "phx/input/input_system.hpp" #include "phx/rendering/auxiliary/splash_screen.hpp" #include "phx/rendering/components/light.hpp" -#include "phx/rendering/components/material_handle.hpp" -#include "phx/rendering/components/mesh_handle.hpp" #include "phx/rendering/components/transform.hpp" #include "phx/rendering/rendering_system.hpp" #include "phx/resources/loaders/assimp_model_loader.hpp" -#include "phx/resources/loaders/openvr_resource_loader.hpp" #include "phx/resources/loaders/scene_loader.hpp" -#include "phx/resources/types/mesh.hpp" -#include "phx/resources/resource_declaration.hpp" #include "phx/resources/resource_manager.hpp" -#include "phx/resources/resource_pointer.hpp" -#include "phx/phoenix.hpp" #include "phx/setup.hpp" -#include "navigation_behavior.hpp" -#include "rotation_behavior.hpp" #include "viewer_system.hpp" +#include "vrcontroller_navigation_behavior.hpp" #if defined __clang__ #pragma clang diagnostic ignored "-Wmissing-prototypes" #endif int main(int, char**) { - std::unique_ptr<phx::Engine> engine = phx::Setup::CreateDefaultEngine(false); + std::unique_ptr<phx::Engine> engine = phx::Setup::CreateDefaultEngine(true); auto scene = engine->GetScene(); auto rendering_system = engine->GetSystem<phx::RenderingSystem>(); rendering_system->SetEnabled(false); phx::SplashScreen* splash = engine->CreateSystem<phx::SplashScreen>( - engine->GetSystem<phx::DisplaySystemWindow>()->GetWindow()); + engine->GetSystem<phx::DisplaySystemWindow>()->GetWindow()); engine->MoveSystemBefore(splash, - engine->GetSystem<phx::DisplaySystemWindow>()); + engine->GetSystem<phx::DisplaySystemWindow>()); auto assimp_loader = static_cast<phx::AssimpModelLoader*>( - phx::ResourceManager::instance().GetLoaderForType(".obj")); + phx::ResourceManager::instance().GetLoaderForType(".obj")); assimp_loader->SetProgressUpdateCallback( - [splash](float progress) { splash->SetLoadProgress(progress); }); + [splash](float progress) { splash->SetLoadProgress(progress); }); phx::InputSystem* input_system = engine->GetSystem<phx::InputSystem>(); ViewerSystem* viewer_system = engine->CreateSystem<ViewerSystem>(); @@ -88,8 +78,8 @@ int main(int, char**) { }); auto handle = std::async([&scene, rendering_system, splash]() { - phx::SceneLoader::InsertModelIntoScene( - "models/UniversityScene/Univers20171013.obj", scene.get()); + phx::SceneLoader::InsertModelIntoScene("models/bunny.obj", scene.get()); + // "models/UniversityScene/Univers20171013.obj" rendering_system->SetEnabled(true); splash->SetEnabled(false); @@ -124,8 +114,9 @@ int main(int, char**) { virtual_platform_transform->SetLocalTranslation(start_position); phx::info("The virtual platform's start position is: {}", start_position); - virtual_platform->AddComponent<NavigationBehavior>( - engine->GetSystem<phx::DisplaySystemOpenVR>()); + virtual_platform->AddComponent<VRControllerNavigationBehavior>( + engine->GetSystem<phx::DeviceSystem>(), + phx::VRController::RIGHT_CONTROLLER); engine->Run(); diff --git a/demos/viewer/src/vrcontroller_navigation_behavior.cpp b/demos/viewer/src/vrcontroller_navigation_behavior.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9acee895c5d9784aa53d6c017de0fd72a8e5b7ba --- /dev/null +++ b/demos/viewer/src/vrcontroller_navigation_behavior.cpp @@ -0,0 +1,99 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#include "vrcontroller_navigation_behavior.hpp" + +#include "phx/suppress_warnings.hpp" + +SUPPRESS_WARNINGS_BEGIN +#include "glm/detail/type_vec3.hpp" +#include "glm/glm.hpp" +#include "glm/gtc/matrix_access.hpp" +SUPPRESS_WARNINGS_END + +#include "phx/core/entity.hpp" +#include "phx/core/logger.hpp" +#include "phx/core/runtime_component.hpp" +#include "phx/display/hmd.hpp" +#include "phx/rendering/components/transform.hpp" + +VRControllerNavigationBehavior::VRControllerNavigationBehavior( + phx::DeviceSystem* device_system, phx::VRController::ControllerSide side) + : device_system_(device_system), side_(side) { + phx::VRController* controller = GetController(); + + if (controller != nullptr) { + controller->RegisterButtonSignal( + [this](phx::VRController::ButtonId id, + phx::VRController::ButtonEvent event) { + this->OnButtonSignal(id, event); + }); + } +} + +void VRControllerNavigationBehavior::OnUpdate() { + const auto transform = GetEntity()->GetFirstComponent<phx::Transform>(); + phx::VRController* controller = GetController(); + if (transform != nullptr && controller != nullptr && button_pressed_) { + glm::mat4 controller_pos = controller->GetPose(); + glm::vec3 controller_forward = glm::column(controller_pos, 2); + float speed = + speed_ * static_cast<float>(time_info_->time_since_last_frame.count()); + speed *= + -1.0f * controller->GetAxesValue(phx::VRController::AXES_TRACKPAD)[1]; + transform->Translate(speed * controller_forward); + } + + phx::info("speed_: {}, axes: {}, time: {}, button_pressed: {}", speed_, + controller->GetAxesValue(phx::VRController::AXES_TRACKPAD), + time_info_->time_since_last_frame.count(), button_pressed_); +} + +void VRControllerNavigationBehavior::OnButtonSignal( + phx::VRController::ButtonId id, phx::VRController::ButtonEvent event) { + if (event == phx::VRController::BUTTON_TOUCH) { + if (id == vr::EVRButtonId::k_EButton_SteamVR_Touchpad) { + button_pressed_ = true; + } + } + if (event == phx::VRController::BUTTON_UNTOUCH && + id == vr::EVRButtonId::k_EButton_SteamVR_Touchpad) { + button_pressed_ = false; + } +} + +float VRControllerNavigationBehavior::GetNavigationSpeed() const { + return speed_; +} + +void VRControllerNavigationBehavior::SetNavigationSpeed(float speed) { + speed_ = speed; +} + +phx::VRController* VRControllerNavigationBehavior::GetController() { + for (auto controller : device_system_->GetDevices<phx::VRController>()) { + if (controller->GetSide() == side_) { + return controller; + } + } + return nullptr; +} diff --git a/demos/viewer/src/vrcontroller_navigation_behavior.hpp b/demos/viewer/src/vrcontroller_navigation_behavior.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2423ca508a6ea343fc986a77e7deb9613ef4afd5 --- /dev/null +++ b/demos/viewer/src/vrcontroller_navigation_behavior.hpp @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef DEMOS_VIEWER_SRC_VRCONTROLLER_NAVIGATION_BEHAVIOR_HPP_ +#define DEMOS_VIEWER_SRC_VRCONTROLLER_NAVIGATION_BEHAVIOR_HPP_ + +#include "phx/input/device_system.hpp" +#include "phx/input/vr_controller.hpp" +#include "phx/rendering/components/transform.hpp" +#include "phx/scripting/behavior.hpp" + +class VRControllerNavigationBehavior : public phx::Behavior { + public: + explicit VRControllerNavigationBehavior( + phx::DeviceSystem* device_system, phx::VRController::ControllerSide side); + VRControllerNavigationBehavior(const VRControllerNavigationBehavior& that) = + default; + VRControllerNavigationBehavior(VRControllerNavigationBehavior&& temp) = + default; + virtual ~VRControllerNavigationBehavior() = default; + VRControllerNavigationBehavior& operator=( + const VRControllerNavigationBehavior& that) = default; + VRControllerNavigationBehavior& operator=( + VRControllerNavigationBehavior&& temp) = default; + + void OnUpdate() override; + void OnButtonSignal(phx::VRController::ButtonId id, + phx::VRController::ButtonEvent event); + + float GetNavigationSpeed() const; + void SetNavigationSpeed(float speed); + + private: + phx::DeviceSystem* device_system_; + phx::VRController* GetController(); + + float speed_ = 0.5f; // m/s + bool button_pressed_ = false; + phx::VRController::ControllerSide side_ = + phx::VRController::INVALID_CONTROLLER; +}; + +#endif // DEMOS_VIEWER_SRC_VRCONTROLLER_NAVIGATION_BEHAVIOR_HPP_ diff --git a/library/phx/display/display_system_openvr.cpp b/library/phx/display/display_system_openvr.cpp index a455ed5a07f6b738c4fd939f5402f22b04e55a8c..be1642e41657adba5f027f9e02f125b0956be080 100644 --- a/library/phx/display/display_system_openvr.cpp +++ b/library/phx/display/display_system_openvr.cpp @@ -33,6 +33,7 @@ #include "phx/core/logger.hpp" #include "phx/core/runtime_component.hpp" #include "phx/core/scene.hpp" +#include "phx/input/device_system.hpp" #include "phx/rendering/rendering_system.hpp" #undef CreateWindow @@ -50,28 +51,20 @@ DisplaySystemOpenVR::DisplaySystemOpenVR(Engine* engine) } DisplaySystemOpenVR::~DisplaySystemOpenVR() {} -phx::HMD* DisplaySystemOpenVR::CreateHMD() { - if (hmd_ == nullptr) { - hmd_ = std::make_unique<HMD>(); - } else { - warn( - "[DisplaySystemOpenVR] HMD already created, so far only one is " - "supported."); - } - return hmd_.get(); +phx::HMD* DisplaySystemOpenVR::GetHMD() { + auto hmds = engine_->GetSystem<DeviceSystem>()->GetDevices<HMD>(); + if (hmds.size() == 0) return nullptr; + return hmds[0]; } -void DisplaySystemOpenVR::DestroyHMD() { hmd_.reset(); } - -phx::HMD* DisplaySystemOpenVR::GetHMD() { return hmd_.get(); } - void DisplaySystemOpenVR::Update(const FrameTimer::TimeInfo&) { - if (hmd_ != nullptr) { + HMD* hmd = GetHMD(); + if (hmd != nullptr) { if (left_render_target_ != nullptr && right_render_target_ != nullptr) { auto right_texture = right_render_target_->GetColorTexture(); - hmd_->Submit(HMD::RIGHT_EYE, right_texture); + hmd->Submit(HMD::RIGHT_EYE, right_texture); auto left_texture = left_render_target_->GetColorTexture(); - hmd_->Submit(HMD::LEFT_EYE, left_texture); + hmd->Submit(HMD::LEFT_EYE, left_texture); } } } diff --git a/library/phx/display/display_system_openvr.hpp b/library/phx/display/display_system_openvr.hpp index eb1c50dace48a0a776c12e78e6e823f779393237..0e195ed9ba54aa8682d84d4853a14074fcee5245 100644 --- a/library/phx/display/display_system_openvr.hpp +++ b/library/phx/display/display_system_openvr.hpp @@ -30,8 +30,8 @@ #include "phx/display/display_system.hpp" #include "phx/display/hmd.hpp" #include "phx/display/window.hpp" -#include "phx/rendering/backend/render_target.hpp" #include "phx/export.hpp" +#include "phx/rendering/backend/render_target.hpp" namespace phx { @@ -45,17 +45,12 @@ class PHOENIX_EXPORT DisplaySystemOpenVR : public DisplaySystem { DisplaySystemOpenVR& operator=(const DisplaySystemOpenVR&) = delete; DisplaySystemOpenVR& operator=(DisplaySystemOpenVR&&) = default; - HMD* CreateHMD(); - void DestroyHMD(); HMD* GetHMD(); void Update(const FrameTimer::TimeInfo&) override; void CreateRenderTargets(Scene* scene); - protected: - std::unique_ptr<HMD> hmd_; - private: void RemoveRenderTargets(); void SetEyeProjections(Scene* scene); diff --git a/library/phx/display/hmd.cpp b/library/phx/display/hmd.cpp index 437a090372151dc070cfd88c423cd44d4fd8407c..f8d72a6ffaf9c5b2cff31fb25c1dad7c02abc725 100644 --- a/library/phx/display/hmd.cpp +++ b/library/phx/display/hmd.cpp @@ -25,7 +25,6 @@ #include <algorithm> #include <array> #include <memory> -#include <stdexcept> #include <string> #include <utility> #include <vector> @@ -42,16 +41,10 @@ SUPPRESS_WARNINGS_END #include "phx/core/logger.hpp" #include "phx/resources/resource_manager.hpp" -#include "phx/resources/resource_pointer.hpp" namespace phx { -HMD::HMD() { - vr::HmdError hmd_error = vr::VRInitError_None; - vr_system_ = vr::VR_Init(&hmd_error, vr::VRApplication_Scene); - if (vr_system_ == nullptr || hmd_error != vr::VRInitError_None) { - error("HMD cannot be initialized with error-code: {}", hmd_error); - throw std::runtime_error("OpenVR cannot be initialized!"); - } +HMD::HMD() : TrackedDevice() { + id_ = vr::k_unTrackedDeviceIndex_Hmd; uint32_t x, y; vr_system_->GetRecommendedRenderTargetSize(&x, &y); @@ -60,12 +53,9 @@ HMD::HMD() { projection_right_ = GetProjectionMatrixFromOpenVR(vr::Hmd_Eye::Eye_Right); projection_left_ = GetProjectionMatrixFromOpenVR(vr::Hmd_Eye::Eye_Left); - eye_to_head_right_ = GetEyeToHeadMatrixFromOpenVR(vr::Hmd_Eye::Eye_Right); - eye_to_head_left_ = GetEyeToHeadMatrixFromOpenVR(vr::Hmd_Eye::Eye_Left); + UpdateEyeToHeadMatrices(); } -HMD::~HMD() { vr::VR_Shutdown(); } - bool HMD::IsHMDPresent() { return vr::VR_IsHmdPresent(); } void HMD::Submit(Side side, gl::texture_2d* texture) { @@ -75,18 +65,7 @@ void HMD::Submit(Side side, gl::texture_2d* texture) { vr::VRCompositor()->Submit(static_cast<vr::EVREye>(side), &vr_texture); } -std::vector<vr::TrackedDeviceIndex_t> HMD::GetControllerIndices() { - std::vector<std::uint32_t> indices(vr::k_unMaxTrackedDeviceCount); - vr::VRSystem()->GetSortedTrackedDeviceIndicesOfClass( - vr::TrackedDeviceClass_Controller, indices.data(), - static_cast<std::uint32_t>(indices.size())); - return indices; -} - -vr::ETrackedControllerRole HMD::GetControllerRoleForTrackedDeviceIndex( - vr::TrackedDeviceIndex_t device_index) const { - return vr_system_->GetControllerRoleForTrackedDeviceIndex(device_index); -} +void HMD::UpdateDeviceIndex() { id_ = vr::k_unTrackedDeviceIndex_Hmd; } const glm::uvec2& HMD::GetViewportSize() const { return viewport_size_; } @@ -104,145 +83,26 @@ const glm::mat4& HMD::GetEyeToHeadMatrix(Side side) const { return eye_to_head_left_; } -void HMD::UpdateTrackedDevices() { - vr::VRCompositor()->WaitGetPoses(tracked_device_poses_, - vr::k_unMaxTrackedDeviceCount, nullptr, 0); -} +void HMD::Update() { + last_wait_get_poses_.resize(vr::k_unMaxTrackedDeviceCount); -glm::mat4 HMD::GetHeadTransformation() { - if (tracked_device_poses_[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid) { - glm::mat4 head = TransformToGlmMatrix( - tracked_device_poses_[vr::k_unTrackedDeviceIndex_Hmd] + vr::VRCompositor()->WaitGetPoses(&last_wait_get_poses_[0], + vr::k_unMaxTrackedDeviceCount, NULL, 0); + if (last_wait_get_poses_[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid) { + pose_ = TransformToGlmMatrix( + last_wait_get_poses_[vr::k_unTrackedDeviceIndex_Hmd] .mDeviceToAbsoluteTracking); - last_head_transformation_ = head; - return head; + } else { + debug("[HMD] HMD pose is invalid, use the last valid one"); } - debug("[HMD] HMD pose is invalid, use the last valid one"); - return last_head_transformation_; -} -glm::mat4 HMD::GetTransformationForRole(vr::ETrackedControllerRole role) { - auto controller_indices = GetControllerIndices(); - for (auto i = 0u; i < controller_indices.size(); ++i) - if (vr_system_->GetControllerRoleForTrackedDeviceIndex( - controller_indices[i]) == role) - return TransformToGlmMatrix(tracked_device_poses_[controller_indices[i]] - .mDeviceToAbsoluteTracking); - debug( - "[HMD::GetTransformationForRole] Unable to find tranformation for role " - "{}", - role); - return glm::mat4(); + TrackedDevice::Update(); } -glm::mat4 HMD::GetLeftControllerTransformation() { - return GetTransformationForRole(vr::TrackedControllerRole_LeftHand); -} - -glm::mat4 HMD::GetRightControllerTransformation() { - return GetTransformationForRole(vr::TrackedControllerRole_RightHand); -} - -std::unique_ptr<phx::Mesh> HMD::GetControllerMesh(Controller controller) { - auto model = GetControllerModel(controller); - if (model == nullptr) { - return nullptr; - } - auto mesh = std::make_unique<phx::Mesh>(); - std::vector<glm::vec3> vertices; - std::vector<glm::vec3> normals; - std::vector<glm::vec2> texcoords; - for (std::size_t i = 0; i < model->unVertexCount; i++) { - vertices.push_back(glm::vec3(model->rVertexData[i].vPosition.v[0], - model->rVertexData[i].vPosition.v[1], - model->rVertexData[i].vPosition.v[2])); - normals.push_back(glm::vec3(model->rVertexData[i].vNormal.v[0], - model->rVertexData[i].vNormal.v[1], - model->rVertexData[i].vNormal.v[2])); - texcoords.push_back(glm::vec2(model->rVertexData[i].rfTextureCoord[0], - model->rVertexData[i].rfTextureCoord[1])); - } - std::vector<unsigned int> indices; - for (std::size_t i = 0; i < model->unTriangleCount * 3; i++) { - indices.push_back(model->rIndexData[i]); +void HMD::OnOpenVREvent(const vr::VREvent_t& event) { + if (event.eventType == vr::VREvent_IpdChanged) { + UpdateEyeToHeadMatrices(); } - mesh->SetVertices(std::move(vertices)); - mesh->SetNormals(std::move(normals)); - mesh->SetTextureCoords(std::move(texcoords)); - mesh->SetIndices(std::move(indices)); - return mesh; -} - -vr::RenderModel_t* HMD::GetControllerModel(Controller controller) { - std::string rendermodel_name; - rendermodel_name.resize(1024); - auto controller_indices = GetControllerIndices(); - for (auto i = 0u; i < controller_indices.size(); ++i) { - if (vr_system_->GetControllerRoleForTrackedDeviceIndex( - controller_indices[i]) == - static_cast<vr::ETrackedControllerRole>(controller)) { - vr::VRSystem()->GetStringTrackedDeviceProperty( - controller_indices[i], vr::Prop_RenderModelName_String, - &rendermodel_name[0], static_cast<uint32_t>(rendermodel_name.size())); - } - } - - vr::RenderModel_t* model = nullptr; - while (vr::VRRenderModels()->LoadRenderModel_Async( - &rendermodel_name[0], &model) == vr::VRRenderModelError_Loading) { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } - if (model == nullptr) { - return nullptr; - } - - return model; -} - -std::unique_ptr<Material> HMD::GetControllerMaterial(Controller controller) { - auto model = GetControllerModel(controller); - if (model == nullptr) return nullptr; - - auto material = std::make_unique<phx::Material>(); - material->SetAmbientColor(glm::vec3(0.1, 0.1, 0.1)); - material->SetSpecularColor(glm::vec3(0.3, 0.3, 0.3)); - material->SetShininess(1.0f); - - auto texture = ResourceManager::instance().DeclareResource<Image>( - {{"TYPE", "openVR"}, - {"OpenVR_type", "texture"}, - {"texture_id", model->diffuseTextureId}, - {"side", - controller == HMD::Controller::LEFT_CONTROLLER ? "left" : "right"}}); - texture.Load(); - material->SetDiffuseImage(texture); - - return material; -} - -std::unique_ptr<Image> HMD::GetControllerTexture(int id) { - vr::RenderModel_TextureMap_t* texture_map; - while (vr::VRRenderModels()->LoadTexture_Async(id, &texture_map) == - vr::VRRenderModelError_Loading) { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } - if (texture_map == nullptr) { - return nullptr; - } - - std::vector<unsigned char> image_data(texture_map->unWidth * - texture_map->unHeight * 4); - std::copy(texture_map->rubTextureMapData, - texture_map->rubTextureMapData + image_data.size(), - image_data.begin()); - auto image = std::make_unique<phx::Image>( - &image_data[0], - std::array<std::size_t, 2>{ - {static_cast<std::size_t>(texture_map->unWidth), - static_cast<std::size_t>(texture_map->unHeight)}}, - 32); - - return image; } glm::mat4 HMD::GetProjectionMatrixFromOpenVR(const vr::Hmd_Eye eye) { @@ -259,18 +119,9 @@ glm::mat4 HMD::GetEyeToHeadMatrixFromOpenVR(const vr::Hmd_Eye eye) { return TransformToGlmMatrix(steamvr_eye_head_matrix); } -glm::mat4 HMD::TransformToGlmMatrix(const vr::HmdMatrix34_t& mat) { - return glm::mat4(mat.m[0][0], mat.m[1][0], mat.m[2][0], 0.0f, mat.m[0][1], - mat.m[1][1], mat.m[2][1], 0.0f, mat.m[0][2], mat.m[1][2], - mat.m[2][2], 0.0f, mat.m[0][3], mat.m[1][3], mat.m[2][3], - 1.0f); -} - -glm::mat4 HMD::TransformToGlmMatrix(const vr::HmdMatrix44_t& mat) { - return glm::mat4(mat.m[0][0], mat.m[1][0], mat.m[2][0], mat.m[3][0], - mat.m[0][1], mat.m[1][1], mat.m[2][1], mat.m[3][1], - mat.m[0][2], mat.m[1][2], mat.m[2][2], mat.m[3][2], - mat.m[0][3], mat.m[1][3], mat.m[2][3], mat.m[3][3]); +void HMD::UpdateEyeToHeadMatrices() { + eye_to_head_right_ = GetEyeToHeadMatrixFromOpenVR(vr::Hmd_Eye::Eye_Right); + eye_to_head_left_ = GetEyeToHeadMatrixFromOpenVR(vr::Hmd_Eye::Eye_Left); } } // namespace phx diff --git a/library/phx/display/hmd.hpp b/library/phx/display/hmd.hpp index 4bf398e9009ead18a97c8aec08fbdce9345be680..be7f5f0f6398c3a2d03ad65af177873e5e9c9628 100644 --- a/library/phx/display/hmd.hpp +++ b/library/phx/display/hmd.hpp @@ -35,20 +35,20 @@ SUPPRESS_WARNINGS_BEGIN #include "openvr.h" //NOLINT SUPPRESS_WARNINGS_END +#include "phx/export.hpp" +#include "phx/input/tracked_device.hpp" #include "phx/resources/types/material.hpp" #include "phx/resources/types/mesh.hpp" -#include "phx/export.hpp" #include "gl/texture.hpp" namespace phx { -class OpenVRResourceLoader; - -class PHOENIX_EXPORT HMD { +class PHOENIX_EXPORT HMD : public TrackedDevice { public: HMD(); - ~HMD(); + ~HMD() = default; + HMD(const HMD&) = delete; HMD(HMD&&) = default; @@ -60,45 +60,25 @@ class PHOENIX_EXPORT HMD { LEFT_EYE = vr::EVREye::Eye_Left }; - enum Controller { - RIGHT_CONTROLLER = vr::TrackedControllerRole_RightHand, - LEFT_CONTROLLER = vr::TrackedControllerRole_LeftHand - }; + void Update() override; + void OnOpenVREvent(const vr::VREvent_t& event) override; const glm::uvec2& GetViewportSize() const; const glm::mat4& GetProjectionMatrix(Side side) const; const glm::mat4& GetEyeToHeadMatrix(Side side) const; - void UpdateTrackedDevices(); - glm::mat4 GetHeadTransformation(); - glm::mat4 GetLeftControllerTransformation(); - glm::mat4 GetRightControllerTransformation(); - static bool IsHMDPresent(); void Submit(Side side, gl::texture_2d* texture); - std::vector<vr::TrackedDeviceIndex_t> GetControllerIndices(); - vr::ETrackedControllerRole GetControllerRoleForTrackedDeviceIndex( - vr::TrackedDeviceIndex_t device_index) const; + void UpdateDeviceIndex() override; private: - glm::mat4 GetTransformationForRole(vr::ETrackedControllerRole role); - glm::mat4 GetProjectionMatrixFromOpenVR(const vr::Hmd_Eye eye); glm::mat4 GetEyeToHeadMatrixFromOpenVR(const vr::Hmd_Eye eye); + void UpdateEyeToHeadMatrices(); - friend OpenVRResourceLoader; - vr::RenderModel_t* GetControllerModel(Controller controller); - std::unique_ptr<Mesh> GetControllerMesh(Controller controller); - std::unique_ptr<Material> GetControllerMaterial(Controller controller); - std::unique_ptr<Image> GetControllerTexture(int id); - - static glm::mat4 TransformToGlmMatrix(const vr::HmdMatrix34_t& mat); - static glm::mat4 TransformToGlmMatrix(const vr::HmdMatrix44_t& mat); - - vr::IVRSystem* vr_system_; glm::uvec2 viewport_size_; glm::mat4 projection_right_; @@ -106,9 +86,6 @@ class PHOENIX_EXPORT HMD { glm::mat4 eye_to_head_right_; glm::mat4 eye_to_head_left_; - vr::TrackedDevicePose_t tracked_device_poses_ - [vr::k_unMaxTrackedDeviceCount]; // NOLINT(runtime/arrays) - glm::mat4 last_head_transformation_; }; diff --git a/library/phx/input/device.cpp b/library/phx/input/device.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5c68859d70073a2d10a384ed647c75afdd66c9b0 --- /dev/null +++ b/library/phx/input/device.cpp @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#include "phx/input/device.hpp" + +namespace phx {} // namespace phx diff --git a/demos/viewer/src/navigation_behavior.hpp b/library/phx/input/device.hpp similarity index 57% rename from demos/viewer/src/navigation_behavior.hpp rename to library/phx/input/device.hpp index aee9accca37a44597cd536ae96edbcb5d6471ab4..c477639764a905efdc712809d5a74034be7aaf98 100644 --- a/demos/viewer/src/navigation_behavior.hpp +++ b/library/phx/input/device.hpp @@ -20,25 +20,19 @@ // limitations under the License. //------------------------------------------------------------------------------ -#ifndef DEMOS_VIEWER_SRC_NAVIGATION_BEHAVIOR_HPP_ -#define DEMOS_VIEWER_SRC_NAVIGATION_BEHAVIOR_HPP_ +#ifndef LIBRARY_PHX_INPUT_DEVICE_HPP_ +#define LIBRARY_PHX_INPUT_DEVICE_HPP_ -#include "phx/display/display_system_openvr.hpp" -#include "phx/scripting/behavior.hpp" +#include "phx/export.hpp" -class NavigationBehavior : public phx::Behavior { - public: - explicit NavigationBehavior(phx::DisplaySystemOpenVR* display_system_openvr); - NavigationBehavior(const NavigationBehavior& that) = default; - NavigationBehavior(NavigationBehavior&& temp) = default; - ~NavigationBehavior() override = default; - NavigationBehavior& operator=(const NavigationBehavior& that) = default; - NavigationBehavior& operator=(NavigationBehavior&& temp) = default; - - void OnUpdate() override; +namespace phx { - protected: - phx::DisplaySystemOpenVR* display_system_openvr_; +class PHOENIX_EXPORT Device { + public: + virtual ~Device() = default; + virtual void Update() = 0; }; -#endif // DEMOS_VIEWER_SRC_NAVIGATION_BEHAVIOR_HPP_ +} // namespace phx + +#endif // LIBRARY_PHX_INPUT_DEVICE_HPP_ diff --git a/library/phx/input/device_system.cpp b/library/phx/input/device_system.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c19d7947ef0a3d69cfecb728c33ddf9de591405a --- /dev/null +++ b/library/phx/input/device_system.cpp @@ -0,0 +1,53 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#include "phx/input/device_system.hpp" + +#include <memory> +#include <string> +#include <utility> + +#include "phx/core/logger.hpp" + +namespace phx { +DeviceSystem::DeviceSystem(Engine* engine) : System(engine) {} + +DeviceSystem::~DeviceSystem() {} + +void DeviceSystem::Update(const FrameTimer::TimeInfo&) { + for (auto& device : devices_) { + device->Update(); + } +} + +void DeviceSystem::RemoveDevice(Device* device) { + auto iterator = + std::remove_if(devices_.begin(), devices_.end(), + [device](const std::unique_ptr<Device>& iteratee) { + return device == iteratee.get(); + }); + devices_.erase(iterator, devices_.end()); +} + +std::string DeviceSystem::ToString() const { return "DeviceSystem"; } + +} // namespace phx diff --git a/library/phx/input/device_system.hpp b/library/phx/input/device_system.hpp new file mode 100644 index 0000000000000000000000000000000000000000..644b8b471f4a3ed5b0ce43f5628dc33c7128ff6d --- /dev/null +++ b/library/phx/input/device_system.hpp @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef LIBRARY_PHX_INPUT_DEVICE_SYSTEM_HPP_ +#define LIBRARY_PHX_INPUT_DEVICE_SYSTEM_HPP_ + +#include <memory> +#include <string> +#include <vector> + +#include "phx/core/engine.hpp" +#include "phx/core/system.hpp" +#include "phx/export.hpp" +#include "phx/input/device.hpp" + +namespace phx { +class InputSystem; + +class PHOENIX_EXPORT DeviceSystem : public System { + public: + DeviceSystem() = delete; + DeviceSystem(const DeviceSystem&) = delete; + DeviceSystem(DeviceSystem&&) = default; + virtual ~DeviceSystem(); + + DeviceSystem& operator=(const DeviceSystem&) = delete; + DeviceSystem& operator=(DeviceSystem&&) = default; + + void Update(const FrameTimer::TimeInfo& time_info) override; + + template <typename DeviceType> + std::vector<DeviceType*> GetDevices() { + static_assert(std::is_base_of<Device, DeviceType>::value, + "The type does not inherit from Device."); + + std::vector<DeviceType*> devices; + for (const auto& device : devices_) { + auto casted_device = dynamic_cast<DeviceType*>(device.get()); + if (casted_device != nullptr) { + devices.push_back(casted_device); + } + } + return devices; + } + + template <typename DeviceType, typename... ComponentArguments> + DeviceType* AddDevice(ComponentArguments&&... arguments) { + devices_.push_back(std::make_unique<DeviceType>(arguments...)); + return dynamic_cast<DeviceType*>(devices_.back().get()); + } + + void RemoveDevice(Device* device); + + std::string ToString() const override; + + private: + friend DeviceSystem* Engine::CreateSystem<DeviceSystem>(); + explicit DeviceSystem(Engine* engine); + + std::vector<std::unique_ptr<Device>> devices_; +}; + +} // namespace phx + +#endif // LIBRARY_PHX_INPUT_DEVICE_SYSTEM_HPP_ diff --git a/library/phx/input/openvr_controller_behavior.cpp b/library/phx/input/openvr_controller_behavior.cpp index cd229828f4a407039f843dc5219e07d7d5c6aaca..fefc8a7c802f0c27dc679072401cffb559017d98 100644 --- a/library/phx/input/openvr_controller_behavior.cpp +++ b/library/phx/input/openvr_controller_behavior.cpp @@ -31,14 +31,14 @@ namespace phx { void OpenVRControllerBehavior::OnUpdate() { phx::Scene* scene = GetEntity()->GetScene(); phx::Entity* runtime_entity = nullptr; - if (side_ == Side::LEFT) { + if (side_ == VRController::LEFT_CONTROLLER) { auto runtime_entities = scene->GetEntitiesWithComponents< phx::RuntimeComponent<phx::LEFT_CONTROLLER>>(); if (!runtime_entities.empty()) { runtime_entity = runtime_entities[0]; } } - if (side_ == Side::RIGHT) { + if (side_ == VRController::RIGHT_CONTROLLER) { auto runtime_entities = scene->GetEntitiesWithComponents< phx::RuntimeComponent<phx::RIGHT_CONTROLLER>>(); if (!runtime_entities.empty()) { @@ -53,9 +53,11 @@ void OpenVRControllerBehavior::OnUpdate() { } } -void OpenVRControllerBehavior::SetSide(Side side) { side_ = side; } +void OpenVRControllerBehavior::SetSide(VRController::ControllerSide side) { + side_ = side; +} -OpenVRControllerBehavior::Side OpenVRControllerBehavior::GetSide() const { +VRController::ControllerSide OpenVRControllerBehavior::GetSide() const { return side_; } diff --git a/library/phx/input/openvr_controller_behavior.hpp b/library/phx/input/openvr_controller_behavior.hpp index 99483e41811c55be517c7eb80fde13b3d20d5153..9f8ce400b7a42d04937543ca2daf4df505361814 100644 --- a/library/phx/input/openvr_controller_behavior.hpp +++ b/library/phx/input/openvr_controller_behavior.hpp @@ -26,20 +26,19 @@ #include "phx/suppress_warnings.hpp" #include "phx/export.hpp" +#include "phx/input/vr_controller.hpp" #include "phx/scripting/behavior.hpp" namespace phx { class PHOENIX_EXPORT OpenVRControllerBehavior : public Behavior { public: - enum Side { LEFT, RIGHT }; - void OnUpdate() override; - void SetSide(Side side); - Side GetSide() const; + void SetSide(VRController::ControllerSide side); + VRController::ControllerSide GetSide() const; private: - Side side_ = Side::LEFT; + VRController::ControllerSide side_ = VRController::LEFT_CONTROLLER; }; } // namespace phx diff --git a/library/phx/input/openvr_controller_system.cpp b/library/phx/input/openvr_controller_model_system.cpp similarity index 75% rename from library/phx/input/openvr_controller_system.cpp rename to library/phx/input/openvr_controller_model_system.cpp index eecf2be7c87c8437f68888ac9670a81bb7123009..643328fc5d8d7169f2b312a6ea9573d3dda354b2 100644 --- a/library/phx/input/openvr_controller_system.cpp +++ b/library/phx/input/openvr_controller_model_system.cpp @@ -20,7 +20,7 @@ // limitations under the License. //------------------------------------------------------------------------------ -#include "phx/input/openvr_controller_system.hpp" +#include "phx/input/openvr_controller_model_system.hpp" #include <memory> #include <string> @@ -29,6 +29,7 @@ #include "phx/core/logger.hpp" #include "phx/display/display_system_openvr.hpp" #include "phx/input/openvr_controller_behavior.hpp" +#include "phx/input/vr_controller.hpp" #include "phx/rendering/components/material_handle.hpp" #include "phx/rendering/components/mesh_handle.hpp" #include "phx/rendering/components/transform.hpp" @@ -38,18 +39,11 @@ namespace phx { -OpenVRControllerSystem::OpenVRControllerSystem(Engine* engine, - DisplaySystem* display_system) - : System(engine) { - // find HMD - hmd_ = dynamic_cast<DisplaySystemOpenVR*>(display_system)->GetHMD(); - if (hmd_ == nullptr) { - error("Cannot use OpenVRControllerSystem without an HMD!"); - return; - } - +OpenVRControllerModelSystem::OpenVRControllerModelSystem( + Engine* engine, DeviceSystem* device_system) + : System(engine), device_system_(device_system) { auto& resource_manager = ResourceManager::instance(); - auto openvr_loader = std::make_unique<OpenVRResourceLoader>(hmd_); + auto openvr_loader = std::make_unique<OpenVRResourceLoader>(device_system); resource_manager.RegisterResourceType("openVR", std::move(openvr_loader)); } @@ -58,11 +52,11 @@ OpenVRControllerSystem::OpenVRControllerSystem(Engine* engine, #pragma clang diagnostic ignored "-Wmissing-prototypes" #endif -Entity* OpenVRControllerSystem::AddController( - phx::Scene* scene, OpenVRControllerBehavior::Side side) { +Entity* OpenVRControllerModelSystem::AddController( + phx::Scene* scene, VRController::ControllerSide side) { auto& resource_manager = ResourceManager::instance(); std::string side_string = - side == OpenVRControllerBehavior::LEFT ? "left" : "right"; + side == VRController::LEFT_CONTROLLER ? "left" : "right"; ResourceDeclaration mesh_declaration{ {"TYPE", "openVR"}, {"OpenVR_type", "mesh"}, {"side", side_string}}; @@ -82,9 +76,9 @@ Entity* OpenVRControllerSystem::AddController( return nullptr; } -Entity* OpenVRControllerSystem::AddControllerEntity( +Entity* OpenVRControllerModelSystem::AddControllerEntity( phx::Scene* scene, ResourcePointer<Mesh> mesh, - ResourcePointer<Material> material, OpenVRControllerBehavior::Side side) { + ResourcePointer<Material> material, VRController::ControllerSide side) { Entity* controller = scene->CreateEntity(); controller->AddComponent<MeshHandle>()->SetMesh(mesh); controller->AddComponent<Transform>(); @@ -98,10 +92,9 @@ Entity* OpenVRControllerSystem::AddControllerEntity( #pragma clang diagnostic pop #endif -void OpenVRControllerSystem::Update(const FrameTimer::TimeInfo&) { +void OpenVRControllerModelSystem::Update(const FrameTimer::TimeInfo&) { // check which controllers are active and update their scene representation, // if necessary - if (!hmd_) return; auto scene = GetEngine()->GetScene(); if (scene == nullptr) return; @@ -112,31 +105,30 @@ void OpenVRControllerSystem::Update(const FrameTimer::TimeInfo&) { GetEngine()->GetEntitiesWithComponents<OpenVRControllerBehavior>(); for (auto entity : controller_entities) { if (entity->GetFirstComponent<OpenVRControllerBehavior>()->GetSide() == - OpenVRControllerBehavior::LEFT) { + VRController::LEFT_CONTROLLER) { left_controller_entity = entity; } else { right_controller_entity = entity; } } - auto controller_indices = hmd_->GetControllerIndices(); bool left_controller_active = false; bool right_controller_active = false; - for (auto idx : controller_indices) { - // is it a left controller? - auto role = hmd_->GetControllerRoleForTrackedDeviceIndex(idx); - if (role == vr::TrackedControllerRole_LeftHand) { + + for (auto controller : device_system_->GetDevices<VRController>()) { + if (!controller->IsActive()) continue; + if (controller->GetSide() == VRController::LEFT_CONTROLLER) { // do we have a left controller in the scene? if (left_controller_entity == nullptr) { // create that controller left_controller_entity = - AddController(scene.get(), OpenVRControllerBehavior::LEFT); + AddController(scene.get(), VRController::LEFT_CONTROLLER); } left_controller_active = true; - } else if (role == vr::TrackedControllerRole_RightHand) { + } else if (controller->GetSide() == VRController::RIGHT_CONTROLLER) { if (right_controller_entity == nullptr) { right_controller_entity = - AddController(scene.get(), OpenVRControllerBehavior::RIGHT); + AddController(scene.get(), VRController::RIGHT_CONTROLLER); } right_controller_active = true; } diff --git a/library/phx/input/openvr_controller_system.hpp b/library/phx/input/openvr_controller_model_system.hpp similarity index 64% rename from library/phx/input/openvr_controller_system.hpp rename to library/phx/input/openvr_controller_model_system.hpp index 607f72a46f02a0db1a33f1457ada5583f27f3e97..693e4d4d864091a269509d3f3f94a082536b5fc6 100644 --- a/library/phx/input/openvr_controller_system.hpp +++ b/library/phx/input/openvr_controller_model_system.hpp @@ -20,8 +20,8 @@ // limitations under the License. //------------------------------------------------------------------------------ -#ifndef LIBRARY_PHX_INPUT_OPENVR_CONTROLLER_SYSTEM_HPP_ -#define LIBRARY_PHX_INPUT_OPENVR_CONTROLLER_SYSTEM_HPP_ +#ifndef LIBRARY_PHX_INPUT_OPENVR_CONTROLLER_MODEL_SYSTEM_HPP_ +#define LIBRARY_PHX_INPUT_OPENVR_CONTROLLER_MODEL_SYSTEM_HPP_ #include <memory> #include <vector> @@ -34,40 +34,43 @@ SUPPRESS_WARNINGS_END #include "phx/core/engine.hpp" #include "phx/core/system.hpp" -#include "phx/display/display_system.hpp" #include "phx/display/hmd.hpp" -#include "phx/input/openvr_controller_behavior.hpp" #include "phx/export.hpp" +#include "phx/input/device_system.hpp" +#include "phx/input/openvr_controller_behavior.hpp" +#include "phx/input/vr_controller.hpp" namespace phx { -class PHOENIX_EXPORT OpenVRControllerSystem : public System { +class PHOENIX_EXPORT OpenVRControllerModelSystem : public System { public: - OpenVRControllerSystem() = delete; - OpenVRControllerSystem(const OpenVRControllerSystem&) = delete; - OpenVRControllerSystem(OpenVRControllerSystem&&) = default; - ~OpenVRControllerSystem() override = default; + OpenVRControllerModelSystem() = delete; + OpenVRControllerModelSystem(const OpenVRControllerModelSystem&) = delete; + OpenVRControllerModelSystem(OpenVRControllerModelSystem&&) = default; + ~OpenVRControllerModelSystem() override = default; - OpenVRControllerSystem& operator=(const OpenVRControllerSystem&) = delete; - OpenVRControllerSystem& operator=(OpenVRControllerSystem&&) = default; + OpenVRControllerModelSystem& operator=(const OpenVRControllerModelSystem&) = + delete; + OpenVRControllerModelSystem& operator=(OpenVRControllerModelSystem&&) = + default; void Update(const FrameTimer::TimeInfo&) override; protected: template <typename SystemType, typename... SystemArguments> friend SystemType* Engine::CreateSystem(SystemArguments&&... arguments); - OpenVRControllerSystem(Engine* engine, DisplaySystem* display_system); + OpenVRControllerModelSystem(Engine* engine, DeviceSystem* device_system); private: static Entity* AddControllerEntity(phx::Scene* scene, ResourcePointer<Mesh> mesh, ResourcePointer<Material> material, - OpenVRControllerBehavior::Side side); + VRController::ControllerSide side); static Entity* AddController(phx::Scene* scene, - OpenVRControllerBehavior::Side side); + VRController::ControllerSide side); - HMD* hmd_ = nullptr; + DeviceSystem* device_system_ = nullptr; }; } // namespace phx -#endif // LIBRARY_PHX_INPUT_OPENVR_CONTROLLER_SYSTEM_HPP_ +#endif // LIBRARY_PHX_INPUT_OPENVR_CONTROLLER_MODEL_SYSTEM_HPP_ diff --git a/library/phx/input/tracked_device.cpp b/library/phx/input/tracked_device.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0800a10dc0068f407972ce018bb7a60a2d17636a --- /dev/null +++ b/library/phx/input/tracked_device.cpp @@ -0,0 +1,139 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#include "phx/input/tracked_device.hpp" + +#include <vector> + +#include "phx/core/entity.hpp" +#include "phx/core/logger.hpp" + +namespace phx { + +vr::IVRSystem* TrackedDevice::vr_system_ = nullptr; +int TrackedDevice::reference_counter_ = 0; +std::mutex TrackedDevice::openVR_init_mutex_; +TrackedDevice::OpenVREventDistributer TrackedDevice::vr_event_distributer; +std::vector<vr::TrackedDevicePose_t> TrackedDevice::last_wait_get_poses_; + +TrackedDevice::TrackedDevice() { + openVR_init_mutex_.lock(); + if (vr_system_ == nullptr) { + vr::EVRInitError init_error = vr::VRInitError_None; + vr_system_ = vr::VR_Init(&init_error, vr::VRApplication_Scene); + if (vr_system_ == nullptr || init_error != vr::VRInitError_None) { + error("OpenVR cannot be initialized with error-code: {}", init_error); + throw std::runtime_error("OpenVR cannot be initialized!"); + } + } + vr_event_distributer.AddDevice(this); + reference_counter_++; + openVR_init_mutex_.unlock(); +} + +TrackedDevice::~TrackedDevice() { + openVR_init_mutex_.lock(); + reference_counter_--; + if (reference_counter_ == 0) { + vr::VR_Shutdown(); + vr_system_ = nullptr; + } + vr_event_distributer.RemoveDevice(this); + openVR_init_mutex_.unlock(); +} + +void TrackedDevice::Update() { + if (id_ == vr::k_unTrackedDeviceIndexInvalid) return; + + vr::TrackedDevicePose_t tracked_device_pose; + if (last_wait_get_poses_.size() == vr::k_unMaxTrackedDeviceCount) { + // the HMD has aquired the poses of all devices with WaitGetPoses() + tracked_device_pose = last_wait_get_poses_[id_]; + } else { + // this tracked device has to get its pose itself directly from openVR + // this method however has a higher lag + vr::VRControllerState_t controller_state; + vr_system_->GetControllerStateWithPose( + vr::TrackingUniverseStanding, id_, &controller_state, + sizeof(controller_state), &tracked_device_pose); + } + if (tracked_device_pose.bPoseIsValid) { + pose_ = TransformToGlmMatrix(tracked_device_pose.mDeviceToAbsoluteTracking); + } else { + debug( + "[TrackedDevice] Tracked Device pose is invalid, device is marked as " + "invalid. The actual implementation has to find another valid index"); + id_ = vr::k_unTrackedDeviceIndexInvalid; + } + + vr_event_distributer.Update(); +} + +glm::mat4 TrackedDevice::GetPose() const { return pose_; } + +bool TrackedDevice::IsActive() const { + return id_ != vr::k_unTrackedDeviceIndexInvalid; +} + +void TrackedDevice::OpenVREventDistributer::AddDevice(TrackedDevice* device) { + tracked_devices_.push_back(device); +} + +void TrackedDevice::OpenVREventDistributer::RemoveDevice( + TrackedDevice* device) { + auto iterator = + std::remove_if(tracked_devices_.begin(), tracked_devices_.end(), + [device](Device* iteratee) { return device == iteratee; }); + tracked_devices_.erase(iterator, tracked_devices_.end()); +} + +void TrackedDevice::OpenVREventDistributer::Update() { + vr::VREvent_t event; + while (vr::VRSystem()->PollNextEvent(&event, sizeof(event))) { + for (TrackedDevice* device : tracked_devices_) { + device->OnOpenVREvent(event); + } + } +} +glm::mat4 TrackedDevice::TransformToGlmMatrix(const vr::HmdMatrix34_t& mat) { + return glm::mat4(mat.m[0][0], mat.m[1][0], mat.m[2][0], 0.0f, mat.m[0][1], + mat.m[1][1], mat.m[2][1], 0.0f, mat.m[0][2], mat.m[1][2], + mat.m[2][2], 0.0f, mat.m[0][3], mat.m[1][3], mat.m[2][3], + 1.0f); +} + +glm::mat4 TrackedDevice::TransformToGlmMatrix(const vr::HmdMatrix44_t& mat) { + return glm::mat4(mat.m[0][0], mat.m[1][0], mat.m[2][0], mat.m[3][0], + mat.m[0][1], mat.m[1][1], mat.m[2][1], mat.m[3][1], + mat.m[0][2], mat.m[1][2], mat.m[2][2], mat.m[3][2], + mat.m[0][3], mat.m[1][3], mat.m[2][3], mat.m[3][3]); +} + +std::vector<vr::TrackedDeviceIndex_t> TrackedDevice::GetDeviceIndicesForClass( + vr::ETrackedDeviceClass device_class) { + std::vector<std::uint32_t> indices(vr::k_unMaxTrackedDeviceCount); + vr::VRSystem()->GetSortedTrackedDeviceIndicesOfClass( + device_class, indices.data(), static_cast<std::uint32_t>(indices.size())); + return indices; +} + +} // namespace phx diff --git a/library/phx/input/tracked_device.hpp b/library/phx/input/tracked_device.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d02805708c3326d5f9e6cbf24954f938b4e8c79f --- /dev/null +++ b/library/phx/input/tracked_device.hpp @@ -0,0 +1,87 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef LIBRARY_PHX_INPUT_TRACKED_DEVICE_HPP_ +#define LIBRARY_PHX_INPUT_TRACKED_DEVICE_HPP_ + +#include <vector> + +#include "phx/input/device.hpp" +#include "phx/resources/types/material.hpp" +#include "phx/resources/types/mesh.hpp" +#include "phx/suppress_warnings.hpp" + +SUPPRESS_WARNINGS_BEGIN +#include "glm/mat4x4.hpp" + +#include "openvr.h" //NOLINT +SUPPRESS_WARNINGS_END + +#include "phx/export.hpp" + +namespace phx { + +class PHOENIX_EXPORT TrackedDevice : public Device { + public: + TrackedDevice(); + virtual ~TrackedDevice(); + + virtual void Update(); + + glm::mat4 GetPose() const; + bool IsActive() const; + + virtual void UpdateDeviceIndex() = 0; + virtual void OnOpenVREvent(const vr::VREvent_t& event) = 0; + + protected: + class OpenVREventDistributer { + public: + void Update(); + void AddDevice(TrackedDevice* device); + void RemoveDevice(TrackedDevice* device); + + private: + std::vector<TrackedDevice*> tracked_devices_; + }; + + static vr::IVRSystem* vr_system_; + static OpenVREventDistributer vr_event_distributer; + static std::vector<vr::TrackedDevicePose_t> last_wait_get_poses_; + glm::mat4 pose_; + + static glm::mat4 TransformToGlmMatrix(const vr::HmdMatrix34_t& mat); + static glm::mat4 TransformToGlmMatrix(const vr::HmdMatrix44_t& mat); + + std::vector<vr::TrackedDeviceIndex_t> GetDeviceIndicesForClass( + vr::ETrackedDeviceClass device_class); + + vr::TrackedDeviceIndex_t id_ = vr::k_unTrackedDeviceIndexInvalid; + + private: + static int reference_counter_; + static std::mutex openVR_init_mutex_; +}; + +} // namespace phx + +#endif // LIBRARY_PHX_INPUT_TRACKED_DEVICE_HPP_ diff --git a/library/phx/input/vr_controller.cpp b/library/phx/input/vr_controller.cpp new file mode 100644 index 0000000000000000000000000000000000000000..97d39c52d8aa704808f833300da0e80049152fa4 --- /dev/null +++ b/library/phx/input/vr_controller.cpp @@ -0,0 +1,141 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#include "phx/input/vr_controller.hpp" + +#include <string> +#include <vector> + +#include "phx/core/logger.hpp" + +namespace phx { + +VRController::VRController(ControllerSide side) : TrackedDevice(), side_(side) { + VRController::Update(); +} + +void VRController::Update() { + if (id_ == vr::k_unTrackedDeviceIndexInvalid || + GetControllerRoleForTrackedDeviceIndex(id_) != side_) { + UpdateDeviceIndex(); + } + TrackedDevice::Update(); +} + +void VRController::OnOpenVREvent(const vr::VREvent_t& event) { + if (event.trackedDeviceIndex != id_) return; + if (event.eventType == vr::VREvent_ButtonPress) { + button_signal_(static_cast<ButtonId>(event.data.controller.button), + ButtonEvent::BUTTON_PRESSED); + } else if (event.eventType == vr::VREvent_ButtonUnpress) { + button_signal_(static_cast<ButtonId>(event.data.controller.button), + ButtonEvent::BUTTON_RELEASED); + } else if (event.eventType == vr::VREvent_ButtonTouch) { + button_signal_(static_cast<ButtonId>(event.data.controller.button), + ButtonEvent::BUTTON_TOUCH); + } else if (event.eventType == vr::VREvent_ButtonUntouch) { + button_signal_(static_cast<ButtonId>(event.data.controller.button), + ButtonEvent::BUTTON_UNTOUCH); + } +} + +void VRController::UpdateDeviceIndex() { + auto controller_indices = + GetDeviceIndicesForClass(vr::TrackedDeviceClass_Controller); + id_ = vr::k_unTrackedDeviceIndexInvalid; + for (auto index : controller_indices) { + if (index != vr::k_unTrackedDeviceIndex_Hmd && + GetControllerRoleForTrackedDeviceIndex(index) == side_) { + id_ = index; + return; + } + } +} + +VRController::ControllerSide VRController::GetSide() const { return side_; } + +vr::RenderModel_t* VRController::GetModel() { + if (id_ == vr::k_unTrackedDeviceIndexInvalid) { + warn("Try to obtain controller model of an invalid controller"); + return nullptr; + } + + std::string rendermodel_name; + rendermodel_name.resize(vr::k_unMaxPropertyStringSize); + + vr::VRSystem()->GetStringTrackedDeviceProperty( + id_, vr::Prop_RenderModelName_String, &rendermodel_name[0], + vr::k_unMaxPropertyStringSize); + + vr::RenderModel_t* model = nullptr; + while (vr::VRRenderModels()->LoadRenderModel_Async( + &rendermodel_name[0], &model) == vr::VRRenderModelError_Loading) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + return model; +} + +boost::signals2::connection VRController::RegisterButtonSignal( + const std::function<void(ButtonId, ButtonEvent)>& callback) { + return button_signal_.connect(callback); +} + +glm::vec2 VRController::GetAxesValue(AxesId axes_id) { + if (!IsActive()) return glm::vec2(0.0f); + + vr::TrackedDevicePose_t tracked_device_pose; + vr::VRControllerState_t controller_state; + vr_system_->GetControllerStateWithPose( + vr::TrackingUniverseStanding, id_, &controller_state, + sizeof(controller_state), &tracked_device_pose); + + for (uint32_t i = 0; i < vr::k_unControllerStateAxisCount; i++) { + vr::EVRControllerAxisType type = static_cast<vr::EVRControllerAxisType>( + vr_system_->GetInt32TrackedDeviceProperty( + id_, static_cast<vr::ETrackedDeviceProperty>( + vr::Prop_Axis0Type_Int32 + i))); + if (type == static_cast<vr::EVRControllerAxisType>(axes_id)) { + vr::VRControllerAxis_t axes = controller_state.rAxis[i]; + return glm::vec2(axes.x, axes.y); + } + } + warn("VRController device does not provide the requested axes: {} ", + vr_system_->GetControllerAxisTypeNameFromEnum( + static_cast<vr::EVRControllerAxisType>(axes_id))); + return glm::vec2(0.0f); +} + +VRController::ControllerSide +VRController::GetControllerRoleForTrackedDeviceIndex( + vr::TrackedDeviceIndex_t device_index) const { + switch (vr_system_->GetControllerRoleForTrackedDeviceIndex(device_index)) { + case vr::TrackedControllerRole_LeftHand: + return LEFT_CONTROLLER; + case vr::TrackedControllerRole_RightHand: + return RIGHT_CONTROLLER; + case vr::TrackedControllerRole_Invalid: + break; + } + return INVALID_CONTROLLER; +} +} // namespace phx diff --git a/library/phx/input/vr_controller.hpp b/library/phx/input/vr_controller.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e9402b97223a14dd81f67606f35316c9be572e9a --- /dev/null +++ b/library/phx/input/vr_controller.hpp @@ -0,0 +1,106 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef LIBRARY_PHX_INPUT_VR_CONTROLLER_HPP_ +#define LIBRARY_PHX_INPUT_VR_CONTROLLER_HPP_ + +#include <memory> + +#define BOOST_BIND_NO_PLACEHOLDERS +// otherwise boosts _ placeholders conflict with trompeloeil ones +#include "boost/signals2/connection.hpp" +#include "boost/signals2/signal.hpp" + +#include "phx/input/tracked_device.hpp" +#include "phx/resources/types/material.hpp" +#include "phx/resources/types/mesh.hpp" +#include "phx/suppress_warnings.hpp" + +SUPPRESS_WARNINGS_BEGIN +#include "glm/mat4x4.hpp" + +#include "openvr.h" //NOLINT +SUPPRESS_WARNINGS_END + +#include "phx/export.hpp" + +namespace phx { + +class OpenVRResourceLoader; + +class PHOENIX_EXPORT VRController : public TrackedDevice { + public: + enum ControllerSide { + RIGHT_CONTROLLER = vr::TrackedControllerRole_RightHand, + LEFT_CONTROLLER = vr::TrackedControllerRole_LeftHand, + INVALID_CONTROLLER = vr::TrackedControllerRole_Invalid + }; + + typedef vr::EVRButtonId ButtonId; + + enum ButtonEvent { + BUTTON_PRESSED, + BUTTON_RELEASED, + BUTTON_TOUCH, + BUTTON_UNTOUCH + }; + + enum AxesId { + AXES_TRACKPAD = vr::k_eControllerAxis_TrackPad, + AXES_JOYSTICK = vr::k_eControllerAxis_Joystick, + AXES_TRIGGER = vr::k_eControllerAxis_Trigger + }; + + explicit VRController(ControllerSide side); + + virtual ~VRController() = default; + + void Update() override; + void OnOpenVREvent(const vr::VREvent_t& event) override; + + void UpdateDeviceIndex() override; + + ControllerSide GetSide() const; + + vr::RenderModel_t* GetModel(); + + boost::signals2::connection RegisterButtonSignal( + const std::function<void(ButtonId, ButtonEvent)>& callback); + + // x ranges from -1.0 to 1.0 for joysticks and track pads and from 0.0 to 1.0 + // for triggers were 0 is fully released. + // y ranges from -1.0 to 1.0 for joysticks and track pads and is always 0.0 + // for triggers. + glm::vec2 GetAxesValue(AxesId id); + + private: + ControllerSide GetControllerRoleForTrackedDeviceIndex( + vr::TrackedDeviceIndex_t device_index) const; + + boost::signals2::signal<void(ButtonId, ButtonEvent)> button_signal_; + + ControllerSide side_; +}; + +} // namespace phx + +#endif // LIBRARY_PHX_INPUT_VR_CONTROLLER_HPP_ diff --git a/library/phx/resources/loaders/openvr_resource_loader.cpp b/library/phx/resources/loaders/openvr_resource_loader.cpp index 3c24d99ef7846cdd8f5e5e6e9005e0bbf055c9e5..230337716f6077f804325f84ba4bc459cb6d415c 100644 --- a/library/phx/resources/loaders/openvr_resource_loader.cpp +++ b/library/phx/resources/loaders/openvr_resource_loader.cpp @@ -22,18 +22,25 @@ #include "phx/resources/loaders/openvr_resource_loader.hpp" +#include <algorithm> #include <memory> #include <string> +#include <utility> +#include <vector> #include "phx/core/logger.hpp" +#include "phx/input/device_system.hpp" +#include "phx/input/vr_controller.hpp" +#include "phx/resources/resource_manager.hpp" #include "phx/resources/types/material.hpp" namespace phx { -OpenVRResourceLoader::OpenVRResourceLoader(HMD *hmd) : hmd_(hmd) {} +OpenVRResourceLoader::OpenVRResourceLoader(DeviceSystem* device_system) + : device_system_(device_system) {} std::unique_ptr<phx::Resource> OpenVRResourceLoader::Load( - const ResourceDeclaration &declaration) { + const ResourceDeclaration& declaration) { if (declaration.find("OpenVR_type") == declaration.end() || !declaration["OpenVR_type"].is_string()) { warn( @@ -43,15 +50,15 @@ std::unique_ptr<phx::Resource> OpenVRResourceLoader::Load( return nullptr; } std::string type = declaration["OpenVR_type"]; - HMD::Controller controller_side = HMD::Controller::LEFT_CONTROLLER; + VRController::ControllerSide controller_side = VRController::LEFT_CONTROLLER; if (declaration.find("side") != declaration.end() && declaration["side"] == "right") { - controller_side = HMD::Controller::RIGHT_CONTROLLER; + controller_side = VRController::RIGHT_CONTROLLER; } // otherwise default left if (type == "material") { - return hmd_->GetControllerMaterial(controller_side); + return GetMaterial(controller_side); } if (type == "texture") { int texture_id = 0; @@ -64,12 +71,11 @@ std::unique_ptr<phx::Resource> OpenVRResourceLoader::Load( } else { texture_id = declaration["texture_id"]; } - return hmd_->GetControllerTexture(texture_id); + return GetTexture(texture_id); } if (type == "mesh") { - return hmd_->GetControllerMesh(controller_side); + return GetMesh(controller_side); } - warn( "OpenVRResource with OpenVR_type {}, cannot be loaded. Full declaration: " "{}", @@ -77,4 +83,92 @@ std::unique_ptr<phx::Resource> OpenVRResourceLoader::Load( return nullptr; } +vr::RenderModel_t* OpenVRResourceLoader::GetModel( + VRController::ControllerSide side) { + for (auto controller : device_system_->GetDevices<VRController>()) { + if (controller->GetSide() == side) return controller->GetModel(); + } + + warn( + "[OpenVRResourceLoader::GetModel] unable to find controller with side {}", + side == VRController::RIGHT_CONTROLLER ? "right" : "left"); + return nullptr; +} + +std::unique_ptr<phx::Mesh> OpenVRResourceLoader::GetMesh( + VRController::ControllerSide side) { + auto model = GetModel(side); + if (model == nullptr) { + return nullptr; + } + auto mesh = std::make_unique<phx::Mesh>(); + std::vector<glm::vec3> vertices; + std::vector<glm::vec3> normals; + std::vector<glm::vec2> texcoords; + for (std::size_t i = 0; i < model->unVertexCount; i++) { + vertices.push_back(glm::vec3(model->rVertexData[i].vPosition.v[0], + model->rVertexData[i].vPosition.v[1], + model->rVertexData[i].vPosition.v[2])); + normals.push_back(glm::vec3(model->rVertexData[i].vNormal.v[0], + model->rVertexData[i].vNormal.v[1], + model->rVertexData[i].vNormal.v[2])); + texcoords.push_back(glm::vec2(model->rVertexData[i].rfTextureCoord[0], + model->rVertexData[i].rfTextureCoord[1])); + } + std::vector<unsigned int> indices; + for (std::size_t i = 0; i < model->unTriangleCount * 3; i++) { + indices.push_back(model->rIndexData[i]); + } + mesh->SetVertices(std::move(vertices)); + mesh->SetNormals(std::move(normals)); + mesh->SetTextureCoords(std::move(texcoords)); + mesh->SetIndices(std::move(indices)); + return mesh; +} + +std::unique_ptr<Material> OpenVRResourceLoader::GetMaterial( + VRController::ControllerSide side) { + auto model = GetModel(side); + if (model == nullptr) return nullptr; + + auto material = std::make_unique<phx::Material>(); + material->SetAmbientColor(glm::vec3(0.1, 0.1, 0.1)); + material->SetSpecularColor(glm::vec3(0.3, 0.3, 0.3)); + + auto texture = ResourceManager::instance().DeclareResource<Image>( + {{"TYPE", "openVR"}, + {"OpenVR_type", "texture"}, + {"texture_id", model->diffuseTextureId}, + {"side", side == VRController::LEFT_CONTROLLER ? "left" : "right"}}); + texture.Load(); + material->SetDiffuseImage(texture); + + return material; +} + +std::unique_ptr<Image> OpenVRResourceLoader::GetTexture(int id) { + vr::RenderModel_TextureMap_t* texture_map; + while (vr::VRRenderModels()->LoadTexture_Async(id, &texture_map) == + vr::VRRenderModelError_Loading) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + if (texture_map == nullptr) { + return nullptr; + } + + std::vector<unsigned char> image_data(texture_map->unWidth * + texture_map->unHeight * 4); + std::copy(texture_map->rubTextureMapData, + texture_map->rubTextureMapData + image_data.size(), + image_data.begin()); + auto image = std::make_unique<phx::Image>( + &image_data[0], + std::array<std::size_t, 2>{ + {static_cast<std::size_t>(texture_map->unWidth), + static_cast<std::size_t>(texture_map->unHeight)}}, + 32); + + return image; +} + } // namespace phx diff --git a/library/phx/resources/loaders/openvr_resource_loader.hpp b/library/phx/resources/loaders/openvr_resource_loader.hpp index 357ff8ad9489e2ac269f962b8a48979d884c26d9..3ccd32ea744e23e21de87a83617d63d739bec047 100644 --- a/library/phx/resources/loaders/openvr_resource_loader.hpp +++ b/library/phx/resources/loaders/openvr_resource_loader.hpp @@ -33,17 +33,18 @@ SUPPRESS_WARNINGS_BEGIN SUPPRESS_WARNINGS_END #include "phx/export.hpp" -#include "phx/display/hmd.hpp" -#include "phx/resources/types/mesh.hpp" +#include "phx/input/device_system.hpp" +#include "phx/input/vr_controller.hpp" #include "phx/resources/resource_declaration.hpp" #include "phx/resources/resource_load_strategy.hpp" +#include "phx/resources/types/mesh.hpp" namespace phx { class Mesh; class PHOENIX_EXPORT OpenVRResourceLoader final : public ResourceLoadStrategy { public: - explicit OpenVRResourceLoader(HMD *hmd); + explicit OpenVRResourceLoader(DeviceSystem *device_system); OpenVRResourceLoader(const OpenVRResourceLoader &) = delete; OpenVRResourceLoader(OpenVRResourceLoader &&) = delete; ~OpenVRResourceLoader() override = default; @@ -53,8 +54,12 @@ class PHOENIX_EXPORT OpenVRResourceLoader final : public ResourceLoadStrategy { std::unique_ptr<Resource> Load(const ResourceDeclaration &file_name) override; - private: - HMD *hmd_; + vr::RenderModel_t *GetModel(VRController::ControllerSide side); + std::unique_ptr<Mesh> GetMesh(VRController::ControllerSide side); + std::unique_ptr<Material> GetMaterial(VRController::ControllerSide side); + std::unique_ptr<Image> GetTexture(int id); + + DeviceSystem *device_system_ = nullptr; }; } // namespace phx diff --git a/library/phx/setup.cpp b/library/phx/setup.cpp index 0c0a862450954383f9621a8cc40f27a5473e5449..895d4eb3251b1257004b50e5222aeb558c984487 100644 --- a/library/phx/setup.cpp +++ b/library/phx/setup.cpp @@ -27,6 +27,8 @@ #include <string> #include <utility> +#include "input/device_system.hpp" +#include "input/vr_controller.hpp" #include "phx/core/component.hpp" #include "phx/core/engine.hpp" #include "phx/core/logger.hpp" @@ -36,7 +38,7 @@ #include "phx/display/display_system_window.hpp" #include "phx/display/hmd.hpp" #include "phx/input/input_system.hpp" -#include "phx/input/openvr_controller_system.hpp" +#include "phx/input/openvr_controller_model_system.hpp" #include "phx/rendering/backend/render_target.hpp" #include "phx/rendering/render_passes/blit_pass.hpp" #include "phx/rendering/render_passes/clear_pass.hpp" @@ -56,16 +58,21 @@ std::unique_ptr<Engine> Setup::CreateDefaultEngine(bool use_hmd_if_available) { engine->CreateSystem<InputSystem>()->AddQuitCallback( [engine_ptr]() { engine_ptr->Stop(); }); + auto device_system = engine->CreateSystem<DeviceSystem>(); + auto displaysys_window = engine->CreateSystem<DisplaySystemWindow>(); - DisplaySystemOpenVR* displaysys_hmd = nullptr; + DisplaySystemOpenVR* displaysys_openVR = nullptr; bool using_hmd = false; if (HMD::IsHMDPresent() && use_hmd_if_available) { info("An HMD is present so we use it"); using_hmd = true; - displaysys_hmd = engine->CreateSystem<DisplaySystemOpenVR>(); - displaysys_hmd->CreateHMD(); + device_system->AddDevice<HMD>(); + device_system->AddDevice<VRController>(VRController::LEFT_CONTROLLER); + device_system->AddDevice<VRController>(VRController::RIGHT_CONTROLLER); + + displaysys_openVR = engine->CreateSystem<DisplaySystemOpenVR>(); } const std::string window_title{using_hmd ? "Phoenix -- HMD Companion" @@ -79,21 +86,24 @@ std::unique_ptr<Engine> Setup::CreateDefaultEngine(bool use_hmd_if_available) { // fix update order engine->MoveSystemToBack(displaysys_window); + if (displaysys_openVR != nullptr) engine->MoveSystemToBack(displaysys_openVR); engine->MoveSystemBefore(rendering_system, displaysys_window); + engine->MoveSystemBefore(device_system, rendering_system); // setup rendering and frame graph - if (using_hmd) { - auto tracking_system = - engine->CreateSystem<TrackingSystemOpenVR>(displaysys_hmd); - auto controller_system = engine->CreateSystem<OpenVRControllerSystem>( - engine->GetSystem<DisplaySystem>()); + if (HMD::IsHMDPresent() && use_hmd_if_available) { + auto controller_model_system = + engine->CreateSystem<OpenVRControllerModelSystem>( + engine->GetSystem<DeviceSystem>()); + auto tracking_system = engine->CreateSystem<TrackingSystemOpenVR>( + engine->GetSystem<DeviceSystem>()); - displaysys_hmd->CreateRenderTargets(engine->GetScene().get()); + displaysys_openVR->CreateRenderTargets(engine->GetScene().get()); SetupDefaultFrameGraphOpenVR(rendering_system, engine.get()); engine->MoveSystemBefore(tracking_system, rendering_system); engine->MoveSystemAfter(behavior_system, tracking_system); - engine->MoveSystemAfter(controller_system, tracking_system); + engine->MoveSystemAfter(controller_model_system, tracking_system); } else { displaysys_window->CreateRenderTarget(engine->GetScene().get(), 68.0f, 0.01f, 1000.0f); diff --git a/library/phx/tracking/tracking_system_openvr.cpp b/library/phx/tracking/tracking_system_openvr.cpp index e101095e0ff421af0d03b59666e79bc2684abb70..8156bc3c4f16e7fb9a100611004a9b28a8f68932 100644 --- a/library/phx/tracking/tracking_system_openvr.cpp +++ b/library/phx/tracking/tracking_system_openvr.cpp @@ -25,23 +25,22 @@ #include <memory> #include <string> -#include "phx/core/logger.hpp" #include "phx/core/runtime_component.hpp" #include "phx/display/display_system_openvr.hpp" #include "phx/display/hmd.hpp" +#include "phx/input/device_system.hpp" +#include "phx/input/vr_controller.hpp" #include "phx/rendering/components/projection.hpp" #include "phx/rendering/components/transform.hpp" namespace phx { TrackingSystemOpenVR::TrackingSystemOpenVR(Engine* engine, - DisplaySystemOpenVR* display_system) + DeviceSystem* device_system) : System(engine) { - if (!display_system) - error("TrackingSystemOpenVR needs a valid DisplaySystemOpenVR."); - if (display_system->GetHMD()) { - CreateRuntimeEntities(engine->GetScene().get()); - scene_changed_connection_ = engine->AddSceneChangedCallback( + if (device_system->GetDevices<HMD>().size() != 0) { + CreateRuntimeEntities(engine_->GetScene().get()); + scene_changed_connection_ = engine_->AddSceneChangedCallback( [this](std::shared_ptr<Scene> old_scene, std::shared_ptr<Scene> new_scene) { OnSceneChanged(old_scene, new_scene); @@ -54,43 +53,34 @@ TrackingSystemOpenVR::~TrackingSystemOpenVR() { } void TrackingSystemOpenVR::Update(const FrameTimer::TimeInfo&) { - const auto dispsys_openvr = engine_->GetSystem<DisplaySystemOpenVR>(); - if (dispsys_openvr == nullptr) { + const auto device_system = engine_->GetSystem<DeviceSystem>(); + if (device_system == nullptr) { return; } - const auto hmd = dispsys_openvr->GetHMD(); - if (hmd == nullptr) { - return; - } - hmd->UpdateTrackedDevices(); - const auto head_transformation = hmd->GetHeadTransformation(); - if (hmd_entity_ != nullptr) { - hmd_entity_->GetFirstComponent<Transform>()->SetLocalMatrix( - head_transformation); - const auto left_eye_transformation = hmd->GetEyeToHeadMatrix(HMD::LEFT_EYE); - left_eye_entity_->GetFirstComponent<Transform>()->SetLocalMatrix( - left_eye_transformation); - const auto right_eye_transformation = - hmd->GetEyeToHeadMatrix(HMD::RIGHT_EYE); - right_eye_entity_->GetFirstComponent<Transform>()->SetLocalMatrix( - right_eye_transformation); - - left_controller_entity_->GetFirstComponent<Transform>()->SetLocalMatrix( - hmd->GetLeftControllerTransformation()); - - right_controller_entity_->GetFirstComponent<Transform>()->SetLocalMatrix( - hmd->GetRightControllerTransformation()); - } + + auto hmds = device_system->GetDevices<HMD>(); + if (hmds.size() != 1) return; + auto hmd = hmds[0]; + + hmd_entity_->GetFirstComponent<Transform>()->SetLocalMatrix(hmd->GetPose()); + left_eye_entity_->GetFirstComponent<Transform>()->SetLocalMatrix( + hmd->GetEyeToHeadMatrix(HMD::LEFT_EYE)); + right_eye_entity_->GetFirstComponent<Transform>()->SetLocalMatrix( + hmd->GetEyeToHeadMatrix(HMD::RIGHT_EYE)); + + left_controller_entity_->GetFirstComponent<Transform>()->SetLocalMatrix( + GetController(VRController::LEFT_CONTROLLER)->GetPose()); + + right_controller_entity_->GetFirstComponent<Transform>()->SetLocalMatrix( + GetController(VRController::RIGHT_CONTROLLER)->GetPose()); } void TrackingSystemOpenVR::CreateRuntimeEntities(Scene* scene) { - const auto hmd = engine_->GetSystem<DisplaySystemOpenVR>()->GetHMD(); - if (hmd == nullptr) { - return; - } - if (scene == nullptr) { + const auto device_system = engine_->GetSystem<DeviceSystem>(); + if (device_system == nullptr || scene == nullptr) { return; } + const auto virtual_platforms = scene->GetEntitiesWithComponents< phx::RuntimeComponent<phx::USER_PLATFORM>>(); if (!virtual_platforms.empty()) { @@ -102,18 +92,18 @@ void TrackingSystemOpenVR::CreateRuntimeEntities(Scene* scene) { hmd_entity_->AddComponent<RuntimeComponent<HEAD>>(); auto hmd_transform = hmd_entity_->AddComponent<Transform>(); hmd_transform->SetParent(virtual_platform_transform); - left_eye_entity_ = scene->CreateEntity(); + left_eye_entity_ = scene->CreateEntity(); left_eye_entity_->AddComponent<RuntimeComponent<LEFT_EYE>>(); auto left_eye_transform = left_eye_entity_->AddComponent<Transform>(); left_eye_transform->SetParent(hmd_transform); left_eye_entity_->AddComponent<Projection>(); - right_eye_entity_ = scene->CreateEntity(); + right_eye_entity_ = scene->CreateEntity(); right_eye_entity_->AddComponent<RuntimeComponent<RIGHT_EYE>>(); auto right_eye_transform = right_eye_entity_->AddComponent<Transform>(); - right_eye_entity_->AddComponent<Projection>(); right_eye_transform->SetParent(hmd_transform); + right_eye_entity_->AddComponent<Projection>(); left_controller_entity_ = scene->CreateEntity(); left_controller_entity_->AddComponent<RuntimeComponent<LEFT_CONTROLLER>>(); @@ -132,18 +122,34 @@ void TrackingSystemOpenVR::CreateRuntimeEntities(Scene* scene) { void TrackingSystemOpenVR::RemoveRuntimeEntities(Scene* scene) { scene->RemoveEntity(hmd_entity_); + hmd_entity_ = nullptr; scene->RemoveEntity(left_eye_entity_); + left_eye_entity_ = nullptr; scene->RemoveEntity(right_eye_entity_); + right_eye_entity_ = nullptr; scene->RemoveEntity(left_controller_entity_); + left_controller_entity_ = nullptr; scene->RemoveEntity(right_controller_entity_); + right_controller_entity_ = nullptr; } void TrackingSystemOpenVR::OnSceneChanged(std::shared_ptr<Scene> old_scene, - std::shared_ptr<Scene> new_scene) { + std::shared_ptr<Scene> new_scene) { RemoveRuntimeEntities(old_scene.get()); CreateRuntimeEntities(new_scene.get()); } +VRController* TrackingSystemOpenVR::GetController( + VRController::ControllerSide side) { + for (auto controller : + engine_->GetSystem<DeviceSystem>()->GetDevices<VRController>()) { + if (controller->GetSide() == side) { + return controller; + } + } + return nullptr; +} + std::string TrackingSystemOpenVR::ToString() const { return "Tracking System"; } } // namespace phx diff --git a/library/phx/tracking/tracking_system_openvr.hpp b/library/phx/tracking/tracking_system_openvr.hpp index d2cd2172a18097ebe5c7d6d48c7720993663a9b1..fd76bce5588bf3018e16ea8fb33dbc9ffbf253a9 100644 --- a/library/phx/tracking/tracking_system_openvr.hpp +++ b/library/phx/tracking/tracking_system_openvr.hpp @@ -30,8 +30,8 @@ #include "phx/core/engine.hpp" #include "phx/core/system.hpp" -#include "phx/display/display_system_openvr.hpp" #include "phx/export.hpp" +#include "phx/input/vr_controller.hpp" SUPPRESS_WARNINGS_BEGIN #define BOOST_BIND_NO_PLACEHOLDERS @@ -39,6 +39,7 @@ SUPPRESS_WARNINGS_BEGIN SUPPRESS_WARNINGS_END namespace phx { +class DeviceSystem; class PHOENIX_EXPORT TrackingSystemOpenVR : public System { public: @@ -55,7 +56,7 @@ class PHOENIX_EXPORT TrackingSystemOpenVR : public System { std::string ToString() const override; protected: - TrackingSystemOpenVR(Engine* engine, DisplaySystemOpenVR* display_system); + TrackingSystemOpenVR(Engine* engine, DeviceSystem* device_system); private: template <typename SystemType, typename... SystemArguments> @@ -66,6 +67,8 @@ class PHOENIX_EXPORT TrackingSystemOpenVR : public System { void OnSceneChanged(std::shared_ptr<Scene> old_scene, std::shared_ptr<Scene> new_scene); + VRController* GetController(VRController::ControllerSide side); + Entity* hmd_entity_ = nullptr; Entity* left_eye_entity_ = nullptr; Entity* right_eye_entity_ = nullptr; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b1ed5f2b63fde5bf248365046d9842d0bcb17323..f63ed905d8e76ba508e35cf4c75092d0c56031d9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -173,73 +173,25 @@ add_test_cpplint(NAME "phoenix-tests--cpplint" # specified via AUTOREMOVE_MOCKED_TEST_SOURCE_FROM autoremove_mocked_test_source_from(${PHOENIX_TEST_SOURCES}) -add_mocked_test(test_clear_pass - LIBRARIES phoenix - MOCKS opengl_mock -) -add_mocked_test(test_input_system - LIBRARIES phoenix - MOCKS openvr_mock sdl_mock -) -add_mocked_test(test_geometry_pass - LIBRARIES phoenix test_utilities - MOCKS opengl_mock sdl_mock -) -add_mocked_test(test_rendering_system - LIBRARIES phoenix - MOCKS opengl_mock sdl_mock -) -add_mocked_test(test_shader - LIBRARIES phoenix - MOCKS opengl_mock -) -add_mocked_test(test_display_system - LIBRARIES phoenix - MOCKS sdl_mock -) -add_mocked_test(test_engine - LIBRARIES phoenix test_utilities - MOCKS opengl_mock openvr_mock sdl_mock -) -add_mocked_test(test_tracking_system - LIBRARIES phoenix test_utilities - MOCKS openvr_mock sdl_mock -) -add_mocked_test(test_openvr_controller_system - LIBRARIES phoenix - MOCKS opengl_mock openvr_mock sdl_mock -) -add_mocked_test(test_assimp_loader - LIBRARIES phoenix test_utilities - MOCKS opengl_mock -) -add_mocked_test(test_model - LIBRARIES phoenix - MOCKS opengl_mock -) -add_mocked_test(test_scene_loader - LIBRARIES phoenix - MOCKS opengl_mock -) - -add_mocked_test(integration_test_model_rendering - LIBRARIES phoenix test_utilities - MOCKS openvr_mock -) -add_mocked_test(integration_test_opengl_buffer_data_download - LIBRARIES phoenix test_utilities - MOCKS openvr_mock -) -add_mocked_test(integration_test_rendering - LIBRARIES phoenix test_utilities - MOCKS openvr_mock -) -add_mocked_test(integration_test_hmd - LIBRARIES phoenix test_utilities - MOCKS openvr_mock -) - - +add_mocked_test(test_clear_pass LIBRARIES phoenix MOCKS opengl_mock) +add_mocked_test(test_input_system LIBRARIES phoenix MOCKS openvr_mock sdl_mock) +add_mocked_test(test_geometry_pass LIBRARIES phoenix test_utilities MOCKS opengl_mock sdl_mock) +add_mocked_test(test_rendering_system LIBRARIES phoenix MOCKS opengl_mock sdl_mock) +add_mocked_test(test_shader LIBRARIES phoenix MOCKS opengl_mock) +add_mocked_test(test_display_system LIBRARIES phoenix MOCKS sdl_mock) +add_mocked_test(test_engine LIBRARIES phoenix test_utilities MOCKS opengl_mock openvr_mock sdl_mock) +add_mocked_test(test_tracking_system LIBRARIES phoenix test_utilities MOCKS openvr_mock sdl_mock) +add_mocked_test(test_openvr_controller_model_system LIBRARIES phoenix MOCKS opengl_mock openvr_mock sdl_mock) +add_mocked_test(test_tracked_device LIBRARIES phoenix MOCKS openvr_mock) +add_mocked_test(test_vr_controller LIBRARIES phoenix test_utilities MOCKS openvr_mock) +add_mocked_test(test_assimp_loader LIBRARIES phoenix test_utilities MOCKS opengl_mock) +add_mocked_test(test_model LIBRARIES phoenix MOCKS opengl_mock) +add_mocked_test(test_scene_loader LIBRARIES phoenix MOCKS opengl_mock) + +add_mocked_test(integration_test_model_rendering LIBRARIES phoenix test_utilities MOCKS openvr_mock) +add_mocked_test(integration_test_opengl_buffer_data_download LIBRARIES phoenix test_utilities MOCKS openvr_mock) +add_mocked_test(integration_test_rendering LIBRARIES phoenix test_utilities MOCKS openvr_mock) +add_mocked_test(integration_test_hmd LIBRARIES phoenix test_utilities MOCKS openvr_mock) get_unmocked_test_sources(PHOENIX_TEST_SOURCES) diff --git a/tests/reference_images/hmd_test_left.png b/tests/reference_images/hmd_test_left.png index 9989f7ae4601b0eb1fcd961ccba454ddf6fdd45d..2f975671215cd6917f0d62a78ec76a8ea84191be 100644 --- a/tests/reference_images/hmd_test_left.png +++ b/tests/reference_images/hmd_test_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4a6d5d6c1eed96056d964381617ec97dd3b6c9d26717cdc55c1e66c8343feed -size 115516 +oid sha256:52c050bf258acaf19f3914bcc2792e4b130cc07e8c7017b8a4c5b69dcccec76f +size 116885 diff --git a/tests/reference_images/hmd_test_right.png b/tests/reference_images/hmd_test_right.png index 33f5fa2758442f23f4bcf04d353ea2e6661cc9a6..2519d5d6775c6b2fec9dbdf1083ac6ad7cd6fbf4 100644 --- a/tests/reference_images/hmd_test_right.png +++ b/tests/reference_images/hmd_test_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e07eb1a9c5bfea732801cbd4b6d6321c7797d815645b23fa205a14fa50d76b3d -size 115419 +oid sha256:a9ea7817c7c1254c1765bf4a0977dd7b430d8419d1355f60b92dce461befae35 +size 116057 diff --git a/tests/src/integration_test_hmd.cpp b/tests/src/integration_test_hmd.cpp index 7babf1ca229f2eb11ea665b3b9ce7ba0b4e2c82d..ee8600182ee2cb73f9084dcdd4d389e9e660ebc0 100644 --- a/tests/src/integration_test_hmd.cpp +++ b/tests/src/integration_test_hmd.cpp @@ -27,10 +27,10 @@ #include "catch/catch.hpp" -#include "phx/suppress_warnings.hpp" - #include "phx/core/engine.hpp" #include "phx/core/runtime_component.hpp" +#include "phx/display/hmd.hpp" +#include "phx/input/device_system.hpp" #include "phx/rendering/backend/opengl_image_buffer_data.hpp" #include "phx/rendering/components/light.hpp" #include "phx/rendering/components/transform.hpp" @@ -38,12 +38,11 @@ #include "phx/resources/resource_utils.hpp" #include "phx/setup.hpp" -SUPPRESS_WARNINGS_BEGIN #include "mocks/openvr_mock.hpp" #include "gl/texture.hpp" -SUPPRESS_WARNINGS_END +#include "test_utilities/glm_mat4.hpp" #include "test_utilities/opengl_buffer_data_comparison.hpp" #include "trompeloeil.hpp" @@ -158,7 +157,6 @@ SCENARIO("If a HMD is present we render for both eyes including controllers", ::MoveUserPlatform(engine.get()); WHEN("We run the engine") { - // TODO(JW): controller models should also be rendered engine->Run(); THEN("Submit() is called for both eyes submitting the expected images") { ::CheckSimilarity(::left_tex_id, "hmd_test_left.png"); @@ -167,3 +165,68 @@ SCENARIO("If a HMD is present we render for both eyes including controllers", } } } + +namespace { + +glm::mat4 ToMat4(float mat[12]) { + glm::mat4 result; + for (int i = 0; i < 12; ++i) { + result[i % 4][i / 4] = mat[i]; + } + return result; +} +} // namespace + +SCENARIO("The Eye to distance is changed via vive's eye distance knob", + "[phx][phx::HMD]") { + GIVEN("A standard engine that runs one frame") { + OPENVR_MOCK_ALLOW_ANY_CALL + phx::Engine engine; + phx::DeviceSystem* device_system = engine.CreateSystem<phx::DeviceSystem>(); + auto hmd = device_system->AddDevice<phx::HMD>(); + + REQUIRE(hmd->GetEyeToHeadMatrix(phx::HMD::LEFT_EYE) == + ToMat4(openvr_mock.eye_to_head_left_)); + REQUIRE(hmd->GetEyeToHeadMatrix(phx::HMD::RIGHT_EYE) == + ToMat4(openvr_mock.eye_to_head_right_)); + + WHEN("We change the eye distance while the engine is running") { + float eye_to_head_left[12] = {1.0f, 0.0f, 0.0f, 0.03f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 12.0f}; + float eye_to_head_right[12] = {1.0f, 0.0f, 0.0f, -0.03f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 13.0f}; + ALLOW_CALL(openvr_mock.GetSystem(), + GetEyeToHeadTransformArray(vr::EVREye::Eye_Left)) + .LR_RETURN(eye_to_head_left); + ALLOW_CALL(openvr_mock.GetSystem(), + GetEyeToHeadTransformArray(vr::EVREye::Eye_Right)) + .LR_RETURN(eye_to_head_right); + THEN("The eye distances are different") { + REQUIRE_FALSE(hmd->GetEyeToHeadMatrix(phx::HMD::LEFT_EYE) == + ToMat4(eye_to_head_left)); + REQUIRE_FALSE(hmd->GetEyeToHeadMatrix(phx::HMD::RIGHT_EYE) == + ToMat4(eye_to_head_right)); + WHEN("The HMD is Updated and an event is received") { + vr::VREvent_t event; + event.eventType = vr::VREvent_IpdChanged; + auto first_call = std::make_shared<bool>(true); + ALLOW_CALL(openvr_mock.GetSystem(), PollNextEvent(_, _)) + .SIDE_EFFECT(*_1 = event) + .SIDE_EFFECT(*first_call = false) + .WITH(*first_call == true) + .RETURN(true); + ALLOW_CALL(openvr_mock.GetSystem(), PollNextEvent(_, _)) + .WITH(*first_call == false) + .RETURN(false); + hmd->Update(); + THEN("The eye to head matrix has changed accordingly") { + REQUIRE(hmd->GetEyeToHeadMatrix(phx::HMD::LEFT_EYE) == + ToMat4(eye_to_head_left)); + REQUIRE(hmd->GetEyeToHeadMatrix(phx::HMD::RIGHT_EYE) == + ToMat4(eye_to_head_right)); + } + } + } + } + } +} diff --git a/tests/src/mocks/openvr_mock.hpp b/tests/src/mocks/openvr_mock.hpp index 855e2b48c5bf4951cc359382ca7a2d55e374535a..2b0d15f92f91a2acb820db4f3f1fd871ba923aeb 100644 --- a/tests/src/mocks/openvr_mock.hpp +++ b/tests/src/mocks/openvr_mock.hpp @@ -114,8 +114,9 @@ class OPENVR_MOCK_EXPORT IVRSystemMock : public IVRSystem { MAKE_MOCK3(GetControllerState, bool(vr::TrackedDeviceIndex_t, vr::VRControllerState_t*, uint32_t)); MAKE_MOCK5(GetControllerStateWithPose, - bool(ETrackingUniverseOrigin, vr::TrackedDeviceIndex_t, - vr::VRControllerState_t*, uint32_t, TrackedDevicePose_t*)); + bool(vr::ETrackingUniverseOrigin, vr::TrackedDeviceIndex_t, + vr::VRControllerState_t*, uint32_t, + vr::TrackedDevicePose_t*)); MAKE_MOCK3(TriggerHapticPulse, void(vr::TrackedDeviceIndex_t, uint32_t, unsigned short)); MAKE_MOCK1(GetButtonIdNameFromEnum, const char*(EVRButtonId)); @@ -401,6 +402,24 @@ extern OPENVR_MOCK_EXPORT OpenVRMock openvr_mock; OpenVRMock::toHMDMatrix34_t(openvr_mock.l_cont_transformation_)) \ .SIDE_EFFECT(_1[openvr_mock.index_left_controller_].bPoseIsValid = \ true); \ + ALLOW_CALL(openvr_mock.GetSystem(), \ + GetControllerStateWithPose(vr::TrackingUniverseStanding, \ + openvr_mock.index_right_controller_, \ + _, _, _)) \ + .RETURN(true) \ + .SIDE_EFFECT( \ + _5->mDeviceToAbsoluteTracking = \ + OpenVRMock::toHMDMatrix34_t(openvr_mock.r_cont_transformation_)) \ + .SIDE_EFFECT(_5->bPoseIsValid = true); \ + ALLOW_CALL( \ + openvr_mock.GetSystem(), \ + GetControllerStateWithPose(vr::TrackingUniverseStanding, \ + openvr_mock.index_left_controller_, _, _, _)) \ + .RETURN(true) \ + .SIDE_EFFECT( \ + _5->mDeviceToAbsoluteTracking = \ + OpenVRMock::toHMDMatrix34_t(openvr_mock.l_cont_transformation_)) \ + .SIDE_EFFECT(_5->bPoseIsValid = true); \ ALLOW_CALL(openvr_mock.GetCompositor(), Submit(_, _, _, _)) \ .RETURN(vr::EVRCompositorError::VRCompositorError_None); \ ALLOW_CALL(openvr_mock.Get(), VR_GetGenericInterface(_, _)) \ @@ -447,6 +466,22 @@ extern OPENVR_MOCK_EXPORT OpenVRMock openvr_mock; ALLOW_CALL(openvr_mock.GetRenderModels(), LoadTexture_Async(_, _)) \ .RETURN(vr::VRRenderModelError_None) \ .SIDE_EFFECT(*_2 = openvr_mock.CreateDummyTexture( \ - static_cast<unsigned int>(_1))); + static_cast<unsigned int>(_1))); \ + ALLOW_CALL(openvr_mock.GetSystem(), PollNextEvent(_, _)).RETURN(false); \ + ALLOW_CALL(openvr_mock.GetSystem(), \ + GetInt32TrackedDeviceProperty(_, vr::Prop_Axis0Type_Int32, _)) \ + .RETURN(vr::k_eControllerAxis_None); \ + ALLOW_CALL(openvr_mock.GetSystem(), \ + GetInt32TrackedDeviceProperty(_, vr::Prop_Axis1Type_Int32, _)) \ + .RETURN(vr::k_eControllerAxis_None); \ + ALLOW_CALL(openvr_mock.GetSystem(), \ + GetInt32TrackedDeviceProperty(_, vr::Prop_Axis2Type_Int32, _)) \ + .RETURN(vr::k_eControllerAxis_None); \ + ALLOW_CALL(openvr_mock.GetSystem(), \ + GetInt32TrackedDeviceProperty(_, vr::Prop_Axis3Type_Int32, _)) \ + .RETURN(vr::k_eControllerAxis_None); \ + ALLOW_CALL(openvr_mock.GetSystem(), \ + GetInt32TrackedDeviceProperty(_, vr::Prop_Axis4Type_Int32, _)) \ + .RETURN(vr::k_eControllerAxis_None); #endif // TESTS_SRC_MOCKS_OPENVR_MOCK_HPP_ diff --git a/tests/src/test-transform.cpp b/tests/src/test-transform.cpp index 38e66cf05e85e2e07bd72e61c4eb7c4a865e166f..a35aea6909dd71327875cae8dd57c77a3cf11f0f 100644 --- a/tests/src/test-transform.cpp +++ b/tests/src/test-transform.cpp @@ -39,7 +39,7 @@ SUPPRESS_WARNINGS_END #include "test_utilities/glm_mat4.hpp" #include "test_utilities/glm_quat.hpp" -#include "test_utilities/glm_vec3.hpp" +#include "test_utilities/glm_vec.hpp" #include "test_utilities/log_capture.hpp" SCENARIO( diff --git a/tests/src/test_device_system.cpp b/tests/src/test_device_system.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dc5b0401fcbaf186a710783e4bc007b8187bb31f --- /dev/null +++ b/tests/src/test_device_system.cpp @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#include <memory> + +#include "catch/catch.hpp" + +#include "trompeloeil.hpp" + +#include "phx/core/engine.hpp" +#include "phx/input/device.hpp" +#include "phx/input/device_system.hpp" + +using trompeloeil::_; +using trompeloeil::ne; + +extern template struct trompeloeil::reporter<trompeloeil::specialized>; + +class MockDevice : public phx::Device { + public: + MAKE_MOCK0(Update, void()); +}; + +class MockDevice2 : public MockDevice { + public: + MAKE_MOCK0(Update, void()); +}; + +SCENARIO("The device system manages and updates its devices", + "[phx][phx::DeviceSystem]") { + GIVEN("an device system") { + phx::Engine engine; + auto device_system = engine.CreateSystem<phx::DeviceSystem>(); + + WHEN("We add devices") { + MockDevice* device1 = device_system->AddDevice<MockDevice>(); + MockDevice2* device2 = device_system->AddDevice<MockDevice2>(); + + THEN("their update method is called when the system is updated") { + REQUIRE_CALL(*device1, Update()).TIMES(1); + REQUIRE_CALL(*device2, Update()).TIMES(1); + device_system->Update(phx::FrameTimer::TimeInfo()); + } + + THEN("we can get these devices by type") { + auto devices = device_system->GetDevices<MockDevice>(); + REQUIRE(devices.size() == 2); + REQUIRE(std::find(devices.begin(), devices.end(), device1) != + devices.end()); + REQUIRE(std::find(devices.begin(), devices.end(), device2) != + devices.end()); + + auto devices2 = device_system->GetDevices<MockDevice2>(); + REQUIRE(devices2.size() == 1); + REQUIRE(devices2[0] == device2); + } + + THEN("devices can be removed again") { + device_system->RemoveDevice(device1); + REQUIRE(device_system->GetDevices<MockDevice>().size() == 1); + } + } + } +} diff --git a/tests/src/test_material.cpp b/tests/src/test_material.cpp index 843bd61e2fde6e39861566ecc0735b02ce29e633..93eb5ccb3eb8442d7417c346af9bea174c996904 100644 --- a/tests/src/test_material.cpp +++ b/tests/src/test_material.cpp @@ -29,7 +29,7 @@ #include "phx/core/logger.hpp" #include "phx/rendering/components/material_handle.hpp" -#include "test_utilities/glm_vec3.hpp" +#include "test_utilities/glm_vec.hpp" SCENARIO("The material component keeps track of its attributes", "[phx][phx::Material]") { diff --git a/tests/src/test_openvr_controller_system.cpp b/tests/src/test_openvr_controller_model_system.cpp similarity index 86% rename from tests/src/test_openvr_controller_system.cpp rename to tests/src/test_openvr_controller_model_system.cpp index 0b2987fe9e6899e0ed6e1434137415228c94a054..1a90a5d68a079ee0d76f4d212626d0083e456326 100644 --- a/tests/src/test_openvr_controller_system.cpp +++ b/tests/src/test_openvr_controller_model_system.cpp @@ -34,9 +34,9 @@ SUPPRESS_WARNINGS_END #include "phx/core/entity.hpp" #include "phx/core/scene.hpp" -#include "phx/display/display_system_openvr.hpp" -#include "phx/input/openvr_controller_behavior.hpp" -#include "phx/input/openvr_controller_system.hpp" +#include "phx/input/device_system.hpp" +#include "phx/input/openvr_controller_model_system.hpp" +#include "phx/input/vr_controller.hpp" #include "phx/rendering/components/material_handle.hpp" #include "phx/rendering/components/mesh_handle.hpp" #include "phx/rendering/rendering_system.hpp" @@ -73,23 +73,23 @@ class EngineStopTestSystem : public phx::System { SCENARIO( "The OpenVRControllerSystem automatically inserts controller entities into " "a scene if controllers are connected", - "[phx][phx::OpenVRControllerSystem]") { + "[phx][phx::OpenVRControllerModelSystem]") { OPENGL_MOCK_ALLOW_ANY_CALL; OPENVR_MOCK_ALLOW_ANY_CALL; SDL_MOCK_ALLOW_ANY_CALL; phx::Engine engine; - auto display_system = engine.CreateSystem<phx::DisplaySystemOpenVR>(); + auto device_system = engine.CreateSystem<phx::DeviceSystem>(); - GIVEN( - "The display system has an HMD and the engine has a scene. Also, there's " - "an OpenVRControllerSystem.") { - display_system->CreateHMD(); + GIVEN("a device system with two vr controllers") { + device_system->AddDevice<phx::VRController>( + phx::VRController::LEFT_CONTROLLER); + device_system->AddDevice<phx::VRController>( + phx::VRController::RIGHT_CONTROLLER); auto scene = std::make_shared<phx::Scene>(); engine.SetScene(scene); - engine.CreateSystem<phx::OpenVRControllerSystem>(display_system); + engine.CreateSystem<phx::OpenVRControllerModelSystem>(device_system); engine.CreateSystem<EngineStopTestSystem>(); - engine.CreateSystem<phx::RenderingSystem>(display_system); WHEN("We run the engine for once frame (updating each system once)") { engine.Run(); diff --git a/tests/src/test_tracked_device.cpp b/tests/src/test_tracked_device.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3166d86284093d5bb116345a8e69eeabdc5d2731 --- /dev/null +++ b/tests/src/test_tracked_device.cpp @@ -0,0 +1,135 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#include <memory> + +#include "catch/catch.hpp" + +#include "phx/display/hmd.hpp" +#include "phx/input/tracked_device.hpp" +#include "phx/input/vr_controller.hpp" + +#include "mocks/openvr_mock.hpp" + +#include "trompeloeil.hpp" + +#undef _ + +extern template struct trompeloeil::reporter<trompeloeil::specialized>; +using trompeloeil::_; + +SCENARIO("OpenVR is only initialized and shutdown once", + "[phx][phx::TrackedDevice]") { + OPENVR_MOCK_ALLOW_ANY_CALL + + WHEN("we initialize 2 derrived tracked devices") { + THEN("openVR is initialized exactly once") { + REQUIRE_CALL(openvr_mock.Get(), + VR_InitInternal2(_, vr::VRApplication_Scene, _)) + .TIMES(1) + .RETURN(static_cast<uint32_t>(1337)) + .SIDE_EFFECT(*_1 = vr::VRInitError_None); + auto device1 = std::make_unique<phx::HMD>(); + auto device2 = std::make_unique<phx::VRController>( + phx::VRController::LEFT_CONTROLLER); + + WHEN("we destroy one") { + THEN("openVR shutdown is not called") { + FORBID_CALL(openvr_mock.Get(), VR_ShutdownInternal()); + device1.reset(); + WHEN("we destroy the second one") { + THEN("openVR shutdown is called") { + REQUIRE_CALL(openvr_mock.Get(), VR_ShutdownInternal()).TIMES(1); + device2.reset(); + } + } + } + } + } + } +} + +#define ALLOW_EVENT_POLL_CALLS \ + ALLOW_CALL(openvr_mock.GetSystem(), PollNextEvent(_, _)) \ + .SIDE_EFFECT(*_1 = event) \ + .SIDE_EFFECT(*first_call = false) \ + .WITH(*first_call == true) \ + .RETURN(true); \ + ALLOW_CALL(openvr_mock.GetSystem(), PollNextEvent(_, _)) \ + .WITH(*first_call == false) \ + .RETURN(false); + +class ActualDevice : public phx::TrackedDevice { + public: + ActualDevice() : TrackedDevice() { id_ = 1; } + void Update() override { TrackedDevice::Update(); } + void UpdateDeviceIndex() override {} + MAKE_MOCK1(OnOpenVREvent, void(const vr::VREvent_t&)); +}; + +SCENARIO("OpenVR events are forwarded as signals to all tracked devices", + "[phx][phx::TrackedDevice]") { + OPENVR_MOCK_ALLOW_ANY_CALL + GIVEN("a derrived tracked device and some OpenVR event") { + auto device = std::make_unique<ActualDevice>(); + + auto first_call = std::make_shared<bool>(true); + vr::VREvent_t event; + event.eventType = vr::EVREventType::VREvent_ButtonPress; + WHEN("Update is called") { + THEN("OnOpenVREvent is called") { + *first_call = true; + ALLOW_EVENT_POLL_CALLS + REQUIRE_CALL(*device, OnOpenVREvent(_)) + .WITH(_1.eventType == vr::EVREventType::VREvent_ButtonPress); + + device->Update(); + } + } + WHEN("we create a second device") { + auto device2 = std::make_unique<ActualDevice>(); + THEN("the onOpenVREvent is called for both devices") { + *first_call = true; + ALLOW_EVENT_POLL_CALLS + REQUIRE_CALL(*device, OnOpenVREvent(_)) + .WITH(_1.eventType == vr::EVREventType::VREvent_ButtonPress); + REQUIRE_CALL(*device2, OnOpenVREvent(_)) + .WITH(_1.eventType == vr::EVREventType::VREvent_ButtonPress); + device->Update(); + device2->Update(); + + WHEN("the first device is deleted") { + device.reset(); + THEN("the second device still gets the event") { + *first_call = true; + ALLOW_EVENT_POLL_CALLS + REQUIRE_CALL(*device2, OnOpenVREvent(_)) + .WITH(_1.eventType == vr::EVREventType::VREvent_ButtonPress); + device2->Update(); + } + } + } + } + } +} + +// the pose etc. is tested in integration_test_hmd diff --git a/tests/src/test_tracking_system.cpp b/tests/src/test_tracking_system.cpp index 44124c76815edf17b45827619708cb257c698973..40de8072af27d14242bdd5fca156463bedf50692 100644 --- a/tests/src/test_tracking_system.cpp +++ b/tests/src/test_tracking_system.cpp @@ -27,6 +27,7 @@ #include "trompeloeil.hpp" +#include "phx/display/hmd.hpp" #include "phx/suppress_warnings.hpp" SUPPRESS_WARNINGS_BEGIN @@ -37,7 +38,7 @@ SUPPRESS_WARNINGS_END #include "phx/core/entity.hpp" #include "phx/core/runtime_component.hpp" #include "phx/core/scene.hpp" -#include "phx/display/display_system_openvr.hpp" +#include "phx/input/device_system.hpp" #include "phx/rendering/components/projection.hpp" #include "phx/rendering/components/transform.hpp" #include "phx/tracking/tracking_system_openvr.hpp" @@ -325,12 +326,17 @@ SCENARIO( SDL_MOCK_ALLOW_ANY_CALL; phx::Engine engine; - auto display_system = engine.CreateSystem<phx::DisplaySystemOpenVR>(); + auto device_system = engine.CreateSystem<phx::DeviceSystem>(); GIVEN( "The display system has an HMD and the engine has a scene with a " "user platform.") { - display_system->CreateHMD(); + device_system->AddDevice<phx::HMD>(); + device_system->AddDevice<phx::VRController>( + phx::VRController::LEFT_CONTROLLER); + device_system->AddDevice<phx::VRController>( + phx::VRController::RIGHT_CONTROLLER); + device_system->Update(phx::FrameTimer::TimeInfo()); auto first_scene = std::make_shared<phx::Scene>(); engine.SetScene(first_scene); auto platform_trans_mat = @@ -339,7 +345,7 @@ SCENARIO( ::SetPlatformMatrix(first_scene.get(), platform_trans_mat); WHEN("A tracking system is created and initialized.") { auto tracking_system = - engine.CreateSystem<phx::TrackingSystemOpenVR>(display_system); + engine.CreateSystem<phx::TrackingSystemOpenVR>(device_system); ::TestRuntimeEntityStructure(first_scene); ::TestRuntimeEntityUpdate(first_scene, tracking_system); diff --git a/tests/src/test_vr_controller.cpp b/tests/src/test_vr_controller.cpp new file mode 100644 index 0000000000000000000000000000000000000000..628db4de69c0c41f45edd95c60b701181d830823 --- /dev/null +++ b/tests/src/test_vr_controller.cpp @@ -0,0 +1,166 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#include <memory> +#include <string> + +#include "catch/catch.hpp" + +#include "phx/core/logger.hpp" +#include "phx/input/vr_controller.hpp" + +#include "mocks/openvr_mock.hpp" + +#include "test_utilities/glm_vec.hpp" +#include "test_utilities/log_capture.hpp" + +#include "trompeloeil.hpp" + +extern template struct trompeloeil::reporter<trompeloeil::specialized>; +using trompeloeil::_; + +namespace { + +auto first_call = std::make_shared<bool>(true); + +void CheckForButtonEvent(uint32_t openVR_event_type, + phx::VRController::ButtonEvent vr_cont_event, + uint32_t button_id, std::string side, + bool expect_to_fire, + phx::VRController* vr_controller) { + WHEN("a " + side + + " openVR event (type: " + std::to_string(openVR_event_type) + + " button id: " + std::to_string(button_id) + " is fired") { + vr::VREvent_t event; + event.eventType = openVR_event_type; + event.data.controller.button = button_id; + event.trackedDeviceIndex = + (side == "left" ? 2 // in the mock 2 is given to the left controller + : 1); // in the mock 1 is given to the right controller + *first_call = true; + ALLOW_CALL(openvr_mock.GetSystem(), PollNextEvent(_, _)) + .SIDE_EFFECT(*_1 = event) + .SIDE_EFFECT(*first_call = false) + .WITH(*first_call == true) + .RETURN(true); + ALLOW_CALL(openvr_mock.GetSystem(), PollNextEvent(_, _)) + .WITH(*first_call == false) + .RETURN(false); + THEN("we expect the signal to " + (expect_to_fire ? "" : "not") + " fire") { + bool signal_fired = false; + vr_controller->RegisterButtonSignal( + [&signal_fired, vr_cont_event, button_id]( + phx::VRController::ButtonId id, + phx::VRController::ButtonEvent input_event) { + if (input_event == vr_cont_event && + id == static_cast<phx::VRController::ButtonId>(button_id)) + signal_fired = true; + }); + vr_controller->Update(); + REQUIRE(signal_fired == expect_to_fire); + } + } +} +} // namespace + +SCENARIO("Button click events of openVR get forwarded as signals", + "[phx][phx::VRController]") { + OPENVR_MOCK_ALLOW_ANY_CALL + GIVEN("a VRController") { + auto vr_controller = + std::make_unique<phx::VRController>(phx::VRController::LEFT_CONTROLLER); + THEN("all button events are mapped correctly") { + CheckForButtonEvent(vr::VREvent_ButtonPress, + phx::VRController::BUTTON_PRESSED, vr::k_EButton_Grip, + "left", true, vr_controller.get()); + CheckForButtonEvent( + vr::VREvent_ButtonUnpress, phx::VRController::BUTTON_RELEASED, + vr::k_EButton_Grip, "left", true, vr_controller.get()); + CheckForButtonEvent(vr::VREvent_ButtonTouch, + phx::VRController::BUTTON_TOUCH, vr::k_EButton_Grip, + "left", true, vr_controller.get()); + CheckForButtonEvent(vr::VREvent_ButtonUntouch, + phx::VRController::BUTTON_UNTOUCH, vr::k_EButton_Grip, + "left", true, vr_controller.get()); + } + THEN("wrong sided events are ignored") { + CheckForButtonEvent(vr::VREvent_ButtonPress, + phx::VRController::BUTTON_PRESSED, vr::k_EButton_Grip, + "right", false, vr_controller.get()); + } + + THEN( + "events on other buttons are ignored, respectively the correct button " + "id is forwarded") { + CheckForButtonEvent( + vr::VREvent_ButtonPress, phx::VRController::BUTTON_PRESSED, + vr::k_EButton_SteamVR_Trigger, "right", false, vr_controller.get()); + } + } +} + +SCENARIO("we can get the actual position on the analog axes, like the trackpad", + "[phx][phx::VRController]") { + OPENVR_MOCK_ALLOW_ANY_CALL + GIVEN("a VRController") { + auto vr_controller = + std::make_unique<phx::VRController>(phx::VRController::LEFT_CONTROLLER); + + WHEN("for an axis is asked that does not exist for this mocked device") { + auto log_capture = std::make_shared<test_utilities::LogCapture>(); + phx::logger = std::make_shared<spdlog::logger>("logcapture", log_capture); + phx::logger->set_pattern("%w"); + ALLOW_CALL(openvr_mock.GetSystem(), GetControllerAxisTypeNameFromEnum( + vr::k_eControllerAxis_Trigger)) + .RETURN("k_eControllerAxis_Trigger"); + + vr_controller->GetAxesValue(phx::VRController::AXES_TRIGGER); + + THEN("we expect a warning") { + REQUIRE(*log_capture == + "VRController device does not provide the requested axes: " + "k_eControllerAxis_Trigger "); + } + } + + WHEN("we ask for an existing axis the values are returned") { + ALLOW_CALL( + openvr_mock.GetSystem(), + GetControllerStateWithPose(vr::TrackingUniverseStanding, 2, _, _, _)) + .RETURN(true) + .SIDE_EFFECT(_3->rAxis[0].x = 0.5f) + .SIDE_EFFECT(_3->rAxis[0].y = 0.7f); + ALLOW_CALL(openvr_mock.GetSystem(), + GetInt32TrackedDeviceProperty(_, vr::Prop_Axis0Type_Int32, _)) + .RETURN(vr::k_eControllerAxis_Trigger); + + glm::vec2 value = + vr_controller->GetAxesValue(phx::VRController::AXES_TRIGGER); + + THEN("we get the expected values") { + REQUIRE(value == glm::vec2(0.5f, 0.7f)); + } + } + } +} + +// TODO(JW) try also axes diff --git a/tests/test_utilities/glm_vec3.hpp b/tests/test_utilities/glm_vec.hpp similarity index 58% rename from tests/test_utilities/glm_vec3.hpp rename to tests/test_utilities/glm_vec.hpp index d33ee272edb8b709e0fdb86e2d6f3f443d5a7f8f..2c56f39eae5c91ba404fcedf5269ada536f32e7f 100644 --- a/tests/test_utilities/glm_vec3.hpp +++ b/tests/test_utilities/glm_vec.hpp @@ -20,8 +20,8 @@ // limitations under the License. //------------------------------------------------------------------------------ -#ifndef TESTS_TEST_UTILITIES_GLM_VEC3_HPP_ -#define TESTS_TEST_UTILITIES_GLM_VEC3_HPP_ +#ifndef TESTS_TEST_UTILITIES_GLM_VEC_HPP_ +#define TESTS_TEST_UTILITIES_GLM_VEC_HPP_ #include <algorithm> #include <limits> @@ -42,6 +42,15 @@ SUPPRESS_WARNINGS_END namespace Catch { +template <> +struct StringMaker<glm::vec2> { + static std::string convert(const glm::vec2& vector) { + std::ostringstream sstr; + sstr << vector; + return sstr.str(); + } +}; + template <> struct StringMaker<glm::vec3> { static std::string convert(const glm::vec3& vector) { @@ -51,25 +60,50 @@ struct StringMaker<glm::vec3> { } }; +template <> +struct StringMaker<glm::vec4> { + static std::string convert(const glm::vec4& vector) { + std::ostringstream sstr; + sstr << vector; + return sstr.str(); + } +}; + } // namespace Catch namespace test_utilities { -template <> -inline bool Approx<glm::vec3>::operator==(const glm::vec3& rhs) { +template <class VectorType> +bool CompareVectors(VectorType a, VectorType b, int rows, float eps, + float margin, float scale) { bool result = true; - for (int row = 0; row < 3; ++row) { - const auto this_element = value_[row]; + for (int row = 0; row < rows; ++row) { + const auto this_element = a[row]; auto this_approx_element = Catch::Detail::Approx(this_element); - this_approx_element.epsilon(epsilon_); - this_approx_element.margin(margin_); - this_approx_element.scale(scale_); - const auto other_element = rhs[row]; + this_approx_element.epsilon(eps); + this_approx_element.margin(margin); + this_approx_element.scale(scale); + const auto other_element = b[row]; result &= (other_element == this_approx_element); } return result; } +template <> +inline bool Approx<glm::vec2>::operator==(const glm::vec2& rhs) { + return CompareVectors<glm::vec2>(value_, rhs, 2, epsilon_, margin_, scale_); +} + +template <> +inline bool Approx<glm::vec3>::operator==(const glm::vec3& rhs) { + return CompareVectors<glm::vec3>(value_, rhs, 3, epsilon_, margin_, scale_); +} + +template <> +inline bool Approx<glm::vec4>::operator==(const glm::vec4& rhs) { + return CompareVectors<glm::vec4>(value_, rhs, 4, epsilon_, margin_, scale_); +} + } // namespace test_utilities -#endif // TESTS_TEST_UTILITIES_GLM_VEC3_HPP_ +#endif // TESTS_TEST_UTILITIES_GLM_VEC_HPP_