diff --git a/demos/viewer/src/viewer.cpp b/demos/viewer/src/viewer.cpp index a5e5dc0390d4c6069bb079a3e661ad919d859a9b..bb293a384a638272b18eca2dd3b178a20fd135c8 100644 --- a/demos/viewer/src/viewer.cpp +++ b/demos/viewer/src/viewer.cpp @@ -52,57 +52,11 @@ #include "phx/transform.hpp" #include "phx/window.hpp" -#include "controller_behavior.hpp" #include "navigation_behavior.hpp" #include "phx/display_system_openvr.hpp" #include "rotation_behavior.hpp" #include "viewer_system.hpp" -void AddControllerEntity(const std::shared_ptr<phx::Scene>& scene, - phx::ResourcePointer<phx::Mesh> mesh, - phx::ResourcePointer<phx::Material> material, - ControllerBehavior::Side side) { - phx::Entity* controller = scene->CreateEntity(); - controller->AddComponent<phx::MeshHandle>()->SetMesh(mesh); - controller->AddComponent<phx::Transform>(); - controller->AddComponent<phx::MaterialHandle>()->SetMaterial(material); - controller->AddComponent<ControllerBehavior>()->SetSide(side); -} - -void AddController(const std::shared_ptr<phx::Scene>& scene, - ControllerBehavior::Side side) { - auto& resource_manager = phx::ResourceManager::instance(); - - phx::ResourceDeclaration mesh_declaration{ - {"TYPE", "openVR"}, - {"OpenVR_type", "mesh"}, - {"side", (side == ControllerBehavior::RIGHT ? "right" : "left")}}; - auto mesh_proxy = - phx::ResourceManager::instance().DeclareResource(mesh_declaration); - mesh_proxy->Load(); - - phx::ResourceDeclaration material_declaration{{"TYPE", "openVR"}, - {"OpenVR_type", "material"}}; - auto material_proxy = resource_manager.DeclareResource(material_declaration); - material_proxy->Load(); - - if (mesh_proxy->GetAs<phx::Mesh>() != nullptr) { - AddControllerEntity(scene, phx::ResourcePointer<phx::Mesh>(mesh_proxy), - phx::ResourcePointer<phx::Material>(material_proxy), - side); - } -} - -void SetupOpenVRController(const std::shared_ptr<phx::Scene>& scene, - phx::HMD* hmd) { - auto& resource_manager = phx::ResourceManager::instance(); - auto openvr_loader = std::make_unique<phx::OpenVRResourceLoader>(hmd); - resource_manager.RegisterResourceType("openVR", std::move(openvr_loader)); - - AddController(scene, ControllerBehavior::RIGHT); - AddController(scene, ControllerBehavior::LEFT); -} - int main(int, char**) { std::unique_ptr<phx::Engine> engine = phx::Setup::CreateDefaultEngine(); auto scene = engine->GetScene(); @@ -126,15 +80,8 @@ int main(int, char**) { viewer_system->SetShowFramerate(!viewer_system->GetShowFramerate()); }); - auto display_system_hmd = engine->GetSystem<phx::DisplaySystemOpenVR>(); - if (display_system_hmd != nullptr) { - auto hmd = display_system_hmd->GetHMD(); - if (hmd != nullptr) { - // SetupOpenVRController(scene, hmd); - } - } - - phx::SceneLoader::InsertModelIntoScene("models/bunny.obj", scene.get()); + phx::SceneLoader::InsertModelIntoScene( + "models/UniversityScene/Univers20171013.obj", scene.get()); std::vector<glm::quat> light_dirs{ glm::quat(glm::angleAxis(-0.25f * glm::pi<float>(), glm::vec3(1, 0, 0))), diff --git a/library/phx/hmd.cpp b/library/phx/hmd.cpp index a4cbcbbda4bfa35d6c9973ebdd0133e3760242a7..4f8c5d3e7edd18133fe39a504e9924a9548f1e20 100644 --- a/library/phx/hmd.cpp +++ b/library/phx/hmd.cpp @@ -80,6 +80,11 @@ std::vector<vr::TrackedDeviceIndex_t> HMD::GetControllerIndices() { return std::move(indices); } +vr::ETrackedControllerRole HMD::GetControllerRoleForTrackedDeviceIndex( + vr::TrackedDeviceIndex_t device_index) const { + return vr_system_->GetControllerRoleForTrackedDeviceIndex(device_index); +} + const glm::uvec2& HMD::GetViewportSize() const { return viewport_size_; } const glm::mat4& HMD::GetProjectionMatrix(Side side) const { @@ -137,7 +142,9 @@ glm::mat4 HMD::GetRightControllerTransformation() { std::unique_ptr<phx::Mesh> HMD::GetControllerMesh(Controller controller) { auto model = GetControllerModel(controller); - if (model == nullptr) return nullptr; + if (model == nullptr) { + return nullptr; + } auto mesh = std::make_unique<phx::Mesh>(); std::vector<glm::vec3> vertices; std::vector<glm::vec3> normals; @@ -177,7 +184,7 @@ vr::RenderModel_t* HMD::GetControllerModel(Controller controller) { } } - vr::RenderModel_t* model; + 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)); @@ -225,9 +232,9 @@ std::unique_ptr<Image> HMD::GetControllerTexture(int id) { texture_map->rubTextureMapData + image_data.size(), image_data.begin()); auto image = std::make_unique<phx::Image>( - image_data, std::array<std::size_t, 2>{ + &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)}}); + static_cast<std::size_t>(texture_map->unHeight)}}, 32); return image; } diff --git a/library/phx/hmd.hpp b/library/phx/hmd.hpp index a3e0b7cfb165fc91f4e9622a6261cf803889fd59..82bedf2cfaf9e305a32b7126ec7415fd6635d45b 100644 --- a/library/phx/hmd.hpp +++ b/library/phx/hmd.hpp @@ -78,6 +78,8 @@ class PHOENIX_EXPORT HMD { void Submit(Side side, gl::texture_2d* texture); std::vector<vr::TrackedDeviceIndex_t> GetControllerIndices(); + vr::ETrackedControllerRole GetControllerRoleForTrackedDeviceIndex( + vr::TrackedDeviceIndex_t device_index) const; private: glm::mat4 GetTransformationForRole(vr::ETrackedControllerRole role); diff --git a/library/phx/image.hpp b/library/phx/image.hpp index 5f686d332ed717a515565219c031645531368082..b9ccc8bcf7a0cb9f6cec1b2c406a9a6f15f1f941 100644 --- a/library/phx/image.hpp +++ b/library/phx/image.hpp @@ -71,7 +71,8 @@ class PHOENIX_EXPORT Image : public Resource, public Loggable { static_cast<std::int32_t>(dimensions[1]), static_cast<std::int32_t>((bits_per_pixel * dimensions[0] + 31) / 32 * 4), - static_cast<std::int32_t>(bits_per_pixel), 0, 0, 0, false); + static_cast<std::int32_t>(bits_per_pixel), FI_RGBA_RED_MASK, + FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, false); if (!native_) { throw std::runtime_error("FreeImage_ConvertFromRawBitsEx failed."); } diff --git a/demos/viewer/src/controller_behavior.cpp b/library/phx/openvr_controller_behavior.cpp similarity index 84% rename from demos/viewer/src/controller_behavior.cpp rename to library/phx/openvr_controller_behavior.cpp index f4fc9a45a6bcdc867ff836d6a039a9039a924f09..51620fe1f74a172e3890a0207fa47f23c91a4383 100644 --- a/demos/viewer/src/controller_behavior.cpp +++ b/library/phx/openvr_controller_behavior.cpp @@ -20,14 +20,15 @@ // limitations under the License. //------------------------------------------------------------------------------ -#include "controller_behavior.hpp" +#include "openvr_controller_behavior.hpp" #include "phx/entity.hpp" #include "phx/runtime_component.hpp" #include "phx/scene.hpp" #include "phx/transform.hpp" -void ControllerBehavior::OnUpdate() { +namespace phx { +void OpenVRControllerBehavior::OnUpdate() { phx::Scene* scene = GetEntity()->GetScene(); phx::Entity* runtime_entity = nullptr; if (side_ == Side::LEFT) { @@ -48,8 +49,14 @@ void ControllerBehavior::OnUpdate() { !(GetEntity()->GetFirstComponent<phx::Transform>()->GetParent() == runtime_entity->GetFirstComponent<phx::Transform>())) { GetEntity()->GetFirstComponent<phx::Transform>()->SetParent( - runtime_entity->GetFirstComponent<phx::Transform>()); + runtime_entity->GetFirstComponent<phx::Transform>(), false); } } -void ControllerBehavior::SetSide(Side side) { side_ = side; } +void OpenVRControllerBehavior::SetSide(Side side) { side_ = side; } + +OpenVRControllerBehavior::Side OpenVRControllerBehavior::GetSide() const { + return side_; +} + +} // namespace phx diff --git a/demos/viewer/src/controller_behavior.hpp b/library/phx/openvr_controller_behavior.hpp similarity index 79% rename from demos/viewer/src/controller_behavior.hpp rename to library/phx/openvr_controller_behavior.hpp index 5f6855c2736df123f19220e24df2f6ccbd571a1a..c8fb9898d17f86bb238273837acb31dcc97df088 100644 --- a/demos/viewer/src/controller_behavior.hpp +++ b/library/phx/openvr_controller_behavior.hpp @@ -20,21 +20,26 @@ // limitations under the License. //------------------------------------------------------------------------------ -#ifndef DEMOS_VIEWER_SRC_CONTROLLER_BEHAVIOR_HPP_ -#define DEMOS_VIEWER_SRC_CONTROLLER_BEHAVIOR_HPP_ +#ifndef LIBRARY_PHX_OPENVR_CONTROLLER_BEHAVIOR_HPP_ +#define LIBRARY_PHX_OPENVR_CONTROLLER_BEHAVIOR_HPP_ #include "phx/behavior.hpp" +#include "phx/export.hpp" -class ControllerBehavior : public phx::Behavior { +namespace phx { + +class PHOENIX_EXPORT OpenVRControllerBehavior : public Behavior { public: enum Side { LEFT, RIGHT }; void OnUpdate() override; void SetSide(Side side); + Side GetSide() const; private: Side side_ = Side::LEFT; }; +} // namespace phx -#endif // DEMOS_VIEWER_SRC_CONTROLLER_BEHAVIOR_HPP_ +#endif // LIBRARY_PHX_OPENVR_CONTROLLER_BEHAVIOR_HPP_ diff --git a/library/phx/openvr_controller_system.cpp b/library/phx/openvr_controller_system.cpp new file mode 100644 index 0000000000000000000000000000000000000000..737f3be65a0201f4929455f29bf46e43456cccc9 --- /dev/null +++ b/library/phx/openvr_controller_system.cpp @@ -0,0 +1,146 @@ +//------------------------------------------------------------------------------ +// 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 "openvr_controller_system.hpp" + +#include <memory> +#include <string> +#include <utility> + +#include "display_system_openvr.hpp" +#include "logger.hpp" +#include "material_handle.hpp" +#include "mesh_handle.hpp" +#include "openvr_controller_behavior.hpp" +#include "openvr_resource_loader.hpp" +#include "resource_manager.hpp" +#include "resource_pointer.hpp" +#include "transform.hpp" + +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; + } + + auto& resource_manager = ResourceManager::instance(); + auto openvr_loader = std::make_unique<OpenVRResourceLoader>(hmd_); + resource_manager.RegisterResourceType("openVR", std::move(openvr_loader)); +} + +Entity* AddControllerEntity(const std::shared_ptr<phx::Scene>& scene, + ResourcePointer<Mesh> mesh, + ResourcePointer<Material> material, + OpenVRControllerBehavior::Side side) { + Entity* controller = scene->CreateEntity(); + controller->AddComponent<MeshHandle>()->SetMesh(mesh); + controller->AddComponent<Transform>(); + controller->AddComponent<MaterialHandle>()->SetMaterial(material); + controller->AddComponent<OpenVRControllerBehavior>()->SetSide(side); + + return controller; +} + +Entity* AddController(const std::shared_ptr<phx::Scene>& scene, + OpenVRControllerBehavior::Side side) { + auto& resource_manager = ResourceManager::instance(); + std::string side_string = + side == OpenVRControllerBehavior::LEFT ? "left" : "right"; + + ResourceDeclaration mesh_declaration{ + {"TYPE", "openVR"}, {"OpenVR_type", "mesh"}, {"side", side_string}}; + auto mesh_proxy = + ResourceManager::instance().DeclareResource(mesh_declaration); + mesh_proxy->Load(); + + ResourceDeclaration material_declaration{ + {"TYPE", "openVR"}, {"OpenVR_type", "material"}, {"side", side_string}}; + auto material_proxy = resource_manager.DeclareResource(material_declaration); + material_proxy->Load(); + + if (mesh_proxy->GetAs<Mesh>() != nullptr) { + return AddControllerEntity(scene, ResourcePointer<Mesh>(mesh_proxy), + ResourcePointer<Material>(material_proxy), side); + } + return nullptr; +} + +void OpenVRControllerSystem::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; + + // get controller entities in the scene + Entity* left_controller_entity = nullptr; + Entity* right_controller_entity = nullptr; + auto controller_entities = + GetEngine()->GetEntitiesWithComponents<OpenVRControllerBehavior>(); + for (auto entity : controller_entities) { + if (entity->GetFirstComponent<OpenVRControllerBehavior>()->GetSide() == + OpenVRControllerBehavior::LEFT) { + 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) { + // do we have a left controller in the scene? + if (left_controller_entity == nullptr) { + // create that controller + left_controller_entity = + AddController(scene, OpenVRControllerBehavior::LEFT); + } + left_controller_active = true; + } else if (role == vr::TrackedControllerRole_RightHand) { + if (right_controller_entity == nullptr) { + right_controller_entity = + AddController(scene, OpenVRControllerBehavior::RIGHT); + } + right_controller_active = true; + } + } + + // remove unnecessary entities + if (!left_controller_active && left_controller_entity != nullptr) { + scene->RemoveEntity(left_controller_entity); + } + if (!right_controller_active && right_controller_entity != nullptr) { + scene->RemoveEntity(right_controller_entity); + } +} + +} // namespace phx diff --git a/library/phx/openvr_controller_system.hpp b/library/phx/openvr_controller_system.hpp new file mode 100644 index 0000000000000000000000000000000000000000..21a3f8b91e243cd6643f34ed9921a31980561ed2 --- /dev/null +++ b/library/phx/openvr_controller_system.hpp @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// 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_OPENVR_CONTROLLER_SYSTEM_HPP_ +#define LIBRARY_PHX_OPENVR_CONTROLLER_SYSTEM_HPP_ + +#include <memory> +#include <vector> + +SUPPRESS_WARNINGS_BEGIN +#include "glm/glm.hpp" +SUPPRESS_WARNINGS_END + +#include "phx/display_system.hpp" +#include "phx/engine.hpp" +#include "phx/export.hpp" +#include "phx/hmd.hpp" +#include "phx/system.hpp" + +namespace phx { +class PHOENIX_EXPORT OpenVRControllerSystem : public System { + public: + OpenVRControllerSystem() = delete; + OpenVRControllerSystem(const OpenVRControllerSystem&) = delete; + OpenVRControllerSystem(OpenVRControllerSystem&&) = default; + virtual ~OpenVRControllerSystem() = default; + + OpenVRControllerSystem& operator=(const OpenVRControllerSystem&) = delete; + OpenVRControllerSystem& operator=(OpenVRControllerSystem&&) = 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); + + private: + HMD* hmd_ = nullptr; +}; + +} // namespace phx + +#endif // LIBRARY_PHX_OPENVR_CONTROLLER_SYSTEM_HPP_ diff --git a/library/phx/setup.cpp b/library/phx/setup.cpp index 05b837d3ae794d2c0ea33a6134acbaf19e8daaae..2b97bad44ba18b34ec81f4a458f3869c44af6318 100644 --- a/library/phx/setup.cpp +++ b/library/phx/setup.cpp @@ -35,6 +35,7 @@ #include "hmd.hpp" #include "input_system.hpp" #include "logger.hpp" +#include "openvr_controller_system.hpp" #include "render_target.hpp" #include "rendering_system.hpp" #include "tracking_system_openvr.hpp" @@ -84,8 +85,11 @@ std::unique_ptr<Engine> Setup::CreateDefaultEngine() { SetupDefaultFrameGraphOpenVR(rendering_system); auto tracking_system = engine->CreateSystem<TrackingSystemOpenVR>(displaysys_hmd); + auto controller_system = engine->CreateSystem<OpenVRControllerSystem>( + engine->GetSystem<DisplaySystem>()); engine->GetUpdateOrder().MoveBefore(tracking_system, rendering_system); engine->GetUpdateOrder().MoveAfter(behavior_system, tracking_system); + engine->GetUpdateOrder().MoveAfter(tracking_system, controller_system); } else { auto render_targets = displaysys_window->CreateRenderTargets(); rendering_system->SetRenderTargets(&render_targets); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0c516001fb8a2bb48d0353a54fac5d8469af3628..14e3a3d43d80b7ada144e5e8a3f81e07489abb90 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -226,6 +226,7 @@ add_mocked_test(test_shader MOCK_GLEW) add_mocked_test(test_display_system MOCK_SDL) add_mocked_test(test_engine MOCK_SDL MOCK_OPENVR MOCK_GLEW) add_mocked_test(test_tracking_system MOCK_SDL MOCK_OPENVR) +add_mocked_test(test_openvr_controller_system MOCK_SDL MOCK_OPENVR MOCK_GLEW) add_mocked_test(integration_test_model_rendering MOCK_OPENVR) add_mocked_test(integration_test_opengl_buffer_data_download MOCK_OPENVR) diff --git a/tests/src/mocks/openvr_mock.hpp b/tests/src/mocks/openvr_mock.hpp index 89ecec4ee202166ee92d3cf35ac315b825a7f496..77881d7054b6d4a82e1777140ec2b34724e70a8c 100644 --- a/tests/src/mocks/openvr_mock.hpp +++ b/tests/src/mocks/openvr_mock.hpp @@ -39,11 +39,11 @@ using trompeloeil::_; class OPENVR_MOCK_EXPORT OpenVRMockInternal { public: MAKE_MOCK0(VR_IsHmdPresent, bool()); - MAKE_MOCK2(VR_GetGenericInterface, void*(const char*, vr::EVRInitError*)); - MAKE_MOCK3(VR_InitInternal2, - uint32_t(vr::EVRInitError*, vr::EVRApplicationType, const char*)); + MAKE_MOCK2(VR_GetGenericInterface, void *(const char *, vr::EVRInitError *)); + MAKE_MOCK3(VR_InitInternal2, uint32_t(vr::EVRInitError *, + vr::EVRApplicationType, const char *)); MAKE_MOCK0(VR_ShutdownInternal, void()); - MAKE_MOCK1(VR_IsInterfaceVersionValid, bool(const char*)); + MAKE_MOCK1(VR_IsInterfaceVersionValid, bool(const char *)); MAKE_MOCK0(VR_GetInitToken, uint32_t()); }; @@ -52,30 +52,31 @@ namespace vr { class OPENVR_MOCK_EXPORT IVRSystemMock : public IVRSystem { public: virtual ~IVRSystemMock() = default; - MAKE_MOCK2(GetRecommendedRenderTargetSize, void(uint32_t*, uint32_t*)); - MAKE_MOCK5(GetProjectionRaw, void(EVREye, float*, float*, float*, float*)); + MAKE_MOCK2(GetRecommendedRenderTargetSize, void(uint32_t *, uint32_t *)); + MAKE_MOCK5(GetProjectionRaw, + void(EVREye, float *, float *, float *, float *)); MAKE_MOCK4(ComputeDistortion, - bool(EVREye, float, float, DistortionCoordinates_t*)); - MAKE_MOCK2(GetTimeSinceLastVsync, bool(float*, uint64_t*)); + bool(EVREye, float, float, DistortionCoordinates_t *)); + MAKE_MOCK2(GetTimeSinceLastVsync, bool(float *, uint64_t *)); MAKE_MOCK0(GetD3D9AdapterIndex, int32_t()); - MAKE_MOCK1(GetDXGIOutputInfo, void(int32_t*)); - MAKE_MOCK3(GetOutputDevice, void(uint64_t*, ETextureType, VkInstance_T*)); + MAKE_MOCK1(GetDXGIOutputInfo, void(int32_t *)); + MAKE_MOCK3(GetOutputDevice, void(uint64_t *, ETextureType, VkInstance_T *)); MAKE_MOCK0(IsDisplayOnDesktop, bool()); MAKE_MOCK1(SetDisplayVisibility, bool(bool)); MAKE_MOCK4(GetDeviceToAbsoluteTrackingPose, - void(ETrackingUniverseOrigin, float, TrackedDevicePose_t*, + void(ETrackingUniverseOrigin, float, TrackedDevicePose_t *, uint32_t)); MAKE_MOCK0(ResetSeatedZeroPose, void()); MAKE_MOCK0(GetSeatedZeroPoseToStandingAbsoluteTrackingPose, HmdMatrix34_t()); MAKE_MOCK0(GetRawZeroPoseToStandingAbsoluteTrackingPose, HmdMatrix34_t()); MAKE_MOCK4(GetSortedTrackedDeviceIndicesOfClass, - uint32_t(ETrackedDeviceClass, TrackedDeviceIndex_t*, uint32_t, + uint32_t(ETrackedDeviceClass, TrackedDeviceIndex_t *, uint32_t, TrackedDeviceIndex_t)); MAKE_MOCK1(GetTrackedDeviceActivityLevel, EDeviceActivityLevel(TrackedDeviceIndex_t)); MAKE_MOCK3(ApplyTransform, - void(TrackedDevicePose_t*, const TrackedDevicePose_t*, - const HmdMatrix34_t*)); + void(TrackedDevicePose_t *, const TrackedDevicePose_t *, + const HmdMatrix34_t *)); MAKE_MOCK1(GetTrackedDeviceIndexForControllerRole, TrackedDeviceIndex_t(vr::ETrackedControllerRole)); MAKE_MOCK1(GetControllerRoleForTrackedDeviceIndex, @@ -85,43 +86,43 @@ class OPENVR_MOCK_EXPORT IVRSystemMock : public IVRSystem { MAKE_MOCK1(IsTrackedDeviceConnected, bool(vr::TrackedDeviceIndex_t)); MAKE_MOCK3(GetBoolTrackedDeviceProperty, bool(vr::TrackedDeviceIndex_t, ETrackedDeviceProperty, - ETrackedPropertyError*)); + ETrackedPropertyError *)); MAKE_MOCK3(GetFloatTrackedDeviceProperty, float(vr::TrackedDeviceIndex_t, ETrackedDeviceProperty, - ETrackedPropertyError*)); + ETrackedPropertyError *)); MAKE_MOCK3(GetInt32TrackedDeviceProperty, int32_t(vr::TrackedDeviceIndex_t, ETrackedDeviceProperty, - ETrackedPropertyError*)); + ETrackedPropertyError *)); MAKE_MOCK3(GetUint64TrackedDeviceProperty, uint64_t(vr::TrackedDeviceIndex_t, ETrackedDeviceProperty, - ETrackedPropertyError*)); + ETrackedPropertyError *)); MAKE_MOCK3(GetMatrix34TrackedDeviceProperty, HmdMatrix34_t(vr::TrackedDeviceIndex_t, ETrackedDeviceProperty, - ETrackedPropertyError*)); + ETrackedPropertyError *)); MAKE_MOCK5(GetStringTrackedDeviceProperty, - uint32_t(vr::TrackedDeviceIndex_t, ETrackedDeviceProperty, char*, - uint32_t, ETrackedPropertyError*)); - MAKE_MOCK1(GetPropErrorNameFromEnum, const char*(ETrackedPropertyError)); - MAKE_MOCK2(PollNextEvent, bool(VREvent_t*, uint32_t)); - MAKE_MOCK4(PollNextEventWithPose, bool(ETrackingUniverseOrigin, VREvent_t*, - uint32_t, vr::TrackedDevicePose_t*)); - MAKE_MOCK1(GetEventTypeNameFromEnum, const char*(EVREventType)); + uint32_t(vr::TrackedDeviceIndex_t, ETrackedDeviceProperty, char *, + uint32_t, ETrackedPropertyError *)); + MAKE_MOCK1(GetPropErrorNameFromEnum, const char *(ETrackedPropertyError)); + MAKE_MOCK2(PollNextEvent, bool(VREvent_t *, uint32_t)); + MAKE_MOCK4(PollNextEventWithPose, bool(ETrackingUniverseOrigin, VREvent_t *, + uint32_t, vr::TrackedDevicePose_t *)); + MAKE_MOCK1(GetEventTypeNameFromEnum, const char *(EVREventType)); MAKE_MOCK2(GetHiddenAreaMesh, HiddenAreaMesh_t(EVREye, EHiddenAreaMeshType)); MAKE_MOCK3(GetControllerState, bool(vr::TrackedDeviceIndex_t, - vr::VRControllerState_t*, uint32_t)); + vr::VRControllerState_t *, uint32_t)); MAKE_MOCK5(GetControllerStateWithPose, bool(ETrackingUniverseOrigin, vr::TrackedDeviceIndex_t, - vr::VRControllerState_t*, uint32_t, TrackedDevicePose_t*)); + vr::VRControllerState_t *, uint32_t, TrackedDevicePose_t *)); MAKE_MOCK3(TriggerHapticPulse, void(vr::TrackedDeviceIndex_t, uint32_t, unsigned short)); - MAKE_MOCK1(GetButtonIdNameFromEnum, const char*(EVRButtonId)); + MAKE_MOCK1(GetButtonIdNameFromEnum, const char *(EVRButtonId)); MAKE_MOCK1(GetControllerAxisTypeNameFromEnum, - const char*(EVRControllerAxisType)); + const char *(EVRControllerAxisType)); MAKE_MOCK0(CaptureInputFocus, bool()); MAKE_MOCK0(ReleaseInputFocus, void()); MAKE_MOCK0(IsInputFocusCapturedByAnotherProcess, bool()); - MAKE_MOCK4(DriverDebugRequest, - uint32_t(vr::TrackedDeviceIndex_t, const char*, char*, uint32_t)); + MAKE_MOCK4(DriverDebugRequest, uint32_t(vr::TrackedDeviceIndex_t, + const char *, char *, uint32_t)); MAKE_MOCK1(PerformFirmwareUpdate, vr::EVRFirmwareError(vr::TrackedDeviceIndex_t)); MAKE_MOCK0(AcknowledgeQuit_Exiting, void()); @@ -130,9 +131,9 @@ class OPENVR_MOCK_EXPORT IVRSystemMock : public IVRSystem { // somehow returning structs is a problem, so this workaround // TODO(WJ): should be reworked once this is solved in trompeloeil // https://github.com/rollbear/trompeloeil/issues/69 - MAKE_MOCK3(GetProjectionMatrixArray, float*(vr::EVREye, float, float)); + MAKE_MOCK3(GetProjectionMatrixArray, float *(vr::EVREye, float, float)); vr::HmdMatrix44_t GetProjectionMatrix(vr::EVREye, float, float); - MAKE_MOCK1(GetEyeToHeadTransformArray, float*(EVREye)); + MAKE_MOCK1(GetEyeToHeadTransformArray, float *(EVREye)); HmdMatrix34_t GetEyeToHeadTransform(EVREye); }; @@ -140,27 +141,28 @@ class OPENVR_MOCK_EXPORT IVRCompositorMock : public IVRCompositor { public: MAKE_MOCK1(SetTrackingSpace, void(ETrackingUniverseOrigin)); MAKE_MOCK0(GetTrackingSpace, ETrackingUniverseOrigin()); - MAKE_MOCK4(WaitGetPoses, EVRCompositorError(TrackedDevicePose_t*, uint32_t, - TrackedDevicePose_t*, uint32_t)); - MAKE_MOCK4(GetLastPoses, EVRCompositorError(TrackedDevicePose_t*, uint32_t, - TrackedDevicePose_t*, uint32_t)); + MAKE_MOCK4(WaitGetPoses, EVRCompositorError(TrackedDevicePose_t *, uint32_t, + TrackedDevicePose_t *, uint32_t)); + MAKE_MOCK4(GetLastPoses, EVRCompositorError(TrackedDevicePose_t *, uint32_t, + TrackedDevicePose_t *, uint32_t)); MAKE_MOCK3(GetLastPoseForTrackedDeviceIndex, - EVRCompositorError(TrackedDeviceIndex_t, TrackedDevicePose_t*, - TrackedDevicePose_t*)); + EVRCompositorError(TrackedDeviceIndex_t, TrackedDevicePose_t *, + TrackedDevicePose_t *)); MAKE_MOCK4(Submit, - EVRCompositorError(EVREye, const Texture_t*, - const VRTextureBounds_t*, EVRSubmitFlags)); + EVRCompositorError(EVREye, const Texture_t *, + const VRTextureBounds_t *, EVRSubmitFlags)); MAKE_MOCK0(ClearLastSubmittedFrame, void()); MAKE_MOCK0(PostPresentHandoff, void()); - MAKE_MOCK2(GetFrameTiming, bool(Compositor_FrameTiming*, uint32_t)); - MAKE_MOCK2(GetFrameTimings, uint32_t(Compositor_FrameTiming*, uint32_t)); + MAKE_MOCK2(GetFrameTiming, bool(Compositor_FrameTiming *, uint32_t)); + MAKE_MOCK2(GetFrameTimings, uint32_t(Compositor_FrameTiming *, uint32_t)); MAKE_MOCK0(GetFrameTimeRemaining, float()); - MAKE_MOCK2(GetCumulativeStats, void(Compositor_CumulativeStats*, uint32_t)); + MAKE_MOCK2(GetCumulativeStats, void(Compositor_CumulativeStats *, uint32_t)); MAKE_MOCK6(FadeToColor, void(float, float, float, float, float, bool)); MAKE_MOCK1(GetCurrentFadeColor, HmdColor_t(bool)); MAKE_MOCK2(FadeGrid, void(float, bool)); MAKE_MOCK0(GetCurrentGridAlpha, float()); - MAKE_MOCK2(SetSkyboxOverride, EVRCompositorError(const Texture_t*, uint32_t)); + MAKE_MOCK2(SetSkyboxOverride, + EVRCompositorError(const Texture_t *, uint32_t)); MAKE_MOCK0(ClearSkyboxOverride, void()); MAKE_MOCK0(CompositorBringToFront, void()); MAKE_MOCK0(CompositorGoToBack, void()); @@ -178,22 +180,62 @@ class OPENVR_MOCK_EXPORT IVRCompositorMock : public IVRCompositor { MAKE_MOCK0(ForceReconnectProcess, void()); MAKE_MOCK1(SuspendRendering, void(bool)); MAKE_MOCK3(GetMirrorTextureD3D11, - EVRCompositorError(vr::EVREye, void*, void**)); - MAKE_MOCK1(ReleaseMirrorTextureD3D11, void(void*)); + EVRCompositorError(vr::EVREye, void *, void **)); + MAKE_MOCK1(ReleaseMirrorTextureD3D11, void(void *)); MAKE_MOCK3(GetMirrorTextureGL, - vr::EVRCompositorError(vr::EVREye, vr::glUInt_t*, - vr::glSharedTextureHandle_t*)); + vr::EVRCompositorError(vr::EVREye, vr::glUInt_t *, + vr::glSharedTextureHandle_t *)); MAKE_MOCK2(ReleaseSharedGLTexture, bool(vr::glUInt_t, vr::glSharedTextureHandle_t)); MAKE_MOCK1(LockGLSharedTextureForAccess, void(vr::glSharedTextureHandle_t)); MAKE_MOCK1(UnlockGLSharedTextureForAccess, void(vr::glSharedTextureHandle_t)); - MAKE_MOCK2(GetVulkanInstanceExtensionsRequired, uint32_t(char*, uint32_t)); + MAKE_MOCK2(GetVulkanInstanceExtensionsRequired, uint32_t(char *, uint32_t)); MAKE_MOCK3(GetVulkanDeviceExtensionsRequired, - uint32_t(VkPhysicalDevice_T*, char*, uint32_t)); + uint32_t(VkPhysicalDevice_T *, char *, uint32_t)); MAKE_MOCK1(SetExplicitTimingMode, void(bool)); MAKE_MOCK0(SubmitExplicitTimingData, EVRCompositorError()); }; +class OPENVR_MOCK_EXPORT IVRRenderModelsMock : public IVRRenderModels { + public: + MAKE_MOCK2(LoadRenderModel_Async, + EVRRenderModelError(const char *, RenderModel_t **)); + MAKE_MOCK1(FreeRenderModel, void(RenderModel_t *)); + MAKE_MOCK2(LoadTexture_Async, + EVRRenderModelError(TextureID_t, RenderModel_TextureMap_t **)); + MAKE_MOCK1(FreeTexture, void(RenderModel_TextureMap_t *)); + MAKE_MOCK3(LoadTextureD3D11_Async, + EVRRenderModelError(TextureID_t, void *, void **)); + MAKE_MOCK2(LoadIntoTextureD3D11_Async, + EVRRenderModelError(TextureID_t, void *)); + MAKE_MOCK1(FreeTextureD3D11, void(void *)); + MAKE_MOCK3(GetRenderModelName, + uint32_t(uint32_t, VR_OUT_STRING() char *, uint32_t)); + MAKE_MOCK0(GetRenderModelCount, uint32_t()); + MAKE_MOCK1(GetComponentCount, uint32_t(const char *)); + MAKE_MOCK4(GetComponentName, uint32_t(const char *, uint32_t, + VR_OUT_STRING() char *, uint32_t)); + + MAKE_MOCK2(GetComponentButtonMask, uint64_t(const char *, const char *)); + MAKE_MOCK4(GetComponentRenderModelName, + uint32_t(const char *, const char *, VR_OUT_STRING() char *, + uint32_t)); + MAKE_MOCK5(GetComponentState, + bool(const char *, const char *, const vr::VRControllerState_t *, + const RenderModel_ControllerMode_State_t *, + RenderModel_ComponentState_t *)); + + MAKE_MOCK2(RenderModelHasComponent, bool(const char *, const char *)); + MAKE_MOCK4(GetRenderModelThumbnailURL, + uint32_t(const char *, VR_OUT_STRING() char *, uint32_t, + vr::EVRRenderModelError *)); + MAKE_MOCK4(GetRenderModelOriginalPath, + uint32_t(const char *, VR_OUT_STRING() char *, uint32_t, + vr::EVRRenderModelError *)); + MAKE_MOCK1(GetRenderModelErrorNameFromEnum, + const char *(vr::EVRRenderModelError)); +}; + } // namespace vr class OPENVR_MOCK_EXPORT OpenVRMock { @@ -201,7 +243,8 @@ class OPENVR_MOCK_EXPORT OpenVRMock { OpenVRMock() : mock_(new OpenVRMockInternal), ivr_system_mock_(new vr::IVRSystemMock), - ivr_compositor_mock_(new vr::IVRCompositorMock) {} + ivr_compositor_mock_(new vr::IVRCompositorMock), + ivr_render_models_mock_(new vr::IVRRenderModelsMock) {} ~OpenVRMock() { // We have to leak the mock_ pointer for the time being. // see @@ -209,9 +252,35 @@ class OPENVR_MOCK_EXPORT OpenVRMock { // TODO(@tvierjahn) regularly check progress on this issue } - OpenVRMockInternal& Get() { return *mock_; } - vr::IVRSystemMock& GetSystem() const { return *ivr_system_mock_; } - vr::IVRCompositorMock& GetCompositor() { return *ivr_compositor_mock_; } + OpenVRMockInternal &Get() { return *mock_; } + vr::IVRSystemMock &GetSystem() const { return *ivr_system_mock_; } + vr::IVRCompositorMock &GetCompositor() { return *ivr_compositor_mock_; } + vr::IVRRenderModelsMock &GetRenderModels() { + return *ivr_render_models_mock_; + } + + static vr::RenderModel_t *CreateDummyTriangleModel() { + vr::RenderModel_t *model = new vr::RenderModel_t(); + model->rIndexData = new uint16_t[3]{0, 1, 2}; + model->unVertexCount = 3; + model->unTriangleCount = 1; + model->diffuseTextureId = -1; + + vr::RenderModel_Vertex_t vertex1; + vertex1.vPosition = {{-0.1f, -0.1f, 0.f}}; + vertex1.vNormal = {{0.f, 0.f, 1.f}}; + vertex1.rfTextureCoord[0] = 0.f; + vertex1.rfTextureCoord[1] = 0.f; + vr::RenderModel_Vertex_t vertex2 = vertex1; + vertex2.vPosition = {{+0.1f, -0.1f, 0.f}}; + vr::RenderModel_Vertex_t vertex3 = vertex1; + vertex2.vPosition = {{0.f, +0.1f, 0.f}}; + + model->rVertexData = + new vr::RenderModel_Vertex_t[3]{vertex1, vertex2, vertex3}; + + return model; + } static vr::HmdMatrix44_t toHMDMatrix44_t(float mat[16]); static vr::HmdMatrix34_t toHMDMatrix34_t(float mat[12]); @@ -229,9 +298,10 @@ class OPENVR_MOCK_EXPORT OpenVRMock { 0.0f, 1.8f, 0.0f, 0.0f, 1.0f, 0.0f}; private: - OpenVRMockInternal* mock_; - vr::IVRSystemMock* ivr_system_mock_; - vr::IVRCompositorMock* ivr_compositor_mock_; + OpenVRMockInternal *mock_; + vr::IVRSystemMock *ivr_system_mock_; + vr::IVRCompositorMock *ivr_compositor_mock_; + vr::IVRRenderModelsMock *ivr_render_models_mock_; }; extern OPENVR_MOCK_EXPORT OpenVRMock openvr_mock; @@ -251,12 +321,14 @@ extern OPENVR_MOCK_EXPORT OpenVRMock openvr_mock; .RETURN(openvr_mock.eye_to_head_right_); \ ALLOW_CALL(openvr_mock.GetSystem(), \ GetSortedTrackedDeviceIndicesOfClass(_, _, _, _)) \ - .RETURN(0u); \ + .RETURN(2u) \ + .SIDE_EFFECT(*_2 = 0u) \ + .SIDE_EFFECT(*(_2 + sizeof(vr::TrackedDeviceIndex_t)) = 1u); \ ALLOW_CALL(openvr_mock.GetSystem(), \ - GetControllerRoleForTrackedDeviceIndex(_)) \ + GetControllerRoleForTrackedDeviceIndex(0u)) \ .RETURN(vr::TrackedControllerRole_LeftHand); \ ALLOW_CALL(openvr_mock.GetSystem(), \ - GetControllerRoleForTrackedDeviceIndex(_)) \ + GetControllerRoleForTrackedDeviceIndex(1u)) \ .RETURN(vr::TrackedControllerRole_RightHand); \ ALLOW_CALL(openvr_mock.GetCompositor(), WaitGetPoses(_, _, _, _)) \ .RETURN(vr::EVRCompositorError::VRCompositorError_None) \ @@ -268,16 +340,28 @@ extern OPENVR_MOCK_EXPORT OpenVRMock openvr_mock; .RETURN(vr::EVRCompositorError::VRCompositorError_None); \ ALLOW_CALL(openvr_mock.Get(), VR_GetGenericInterface(_, _)) \ .WITH(std::string(vr::IVRSystem_Version) == std::string(_1)) \ - .RETURN(static_cast<void*>(&openvr_mock.GetSystem())); \ + .RETURN(static_cast<void *>(&openvr_mock.GetSystem())); \ + ALLOW_CALL(openvr_mock.Get(), VR_GetGenericInterface(_, _)) \ + .WITH(std::string(vr::IVRRenderModels_Version) == std::string(_1)) \ + .RETURN(static_cast<void *>(&openvr_mock.GetRenderModels())); \ ALLOW_CALL(openvr_mock.Get(), VR_GetGenericInterface(_, _)) \ .WITH(std::string(vr::IVRCompositor_Version) == std::string(_1)) \ - .RETURN(static_cast<void*>(&openvr_mock.GetCompositor())); \ + .RETURN(static_cast<void *>(&openvr_mock.GetCompositor())); \ ALLOW_CALL(openvr_mock.Get(), \ VR_InitInternal2(_, vr::VRApplication_Scene, _)) \ .RETURN(static_cast<uint32_t>(1337)) \ .SIDE_EFFECT(*_1 = vr::VRInitError_None); \ ALLOW_CALL(openvr_mock.Get(), VR_ShutdownInternal()); \ ALLOW_CALL(openvr_mock.Get(), VR_IsInterfaceVersionValid(_)).RETURN(true); \ - ALLOW_CALL(openvr_mock.Get(), VR_GetInitToken()).RETURN(1337); + ALLOW_CALL(openvr_mock.Get(), VR_GetInitToken()).RETURN(1337); \ + ALLOW_CALL(openvr_mock.GetRenderModels(), LoadRenderModel_Async(_, _)) \ + .RETURN(vr::VRRenderModelError_None) \ + .SIDE_EFFECT(*_2 = openvr_mock.CreateDummyTriangleModel()); \ + ALLOW_CALL(openvr_mock.GetSystem(), \ + GetStringTrackedDeviceProperty(_, _, _, _, _)) \ + .RETURN(0); \ + ALLOW_CALL(openvr_mock.GetRenderModels(), LoadTexture_Async(_, _)) \ + .RETURN(vr::VRRenderModelError_None) \ + .SIDE_EFFECT(*_2 = nullptr); #endif // TESTS_SRC_MOCKS_OPENVR_MOCK_HPP_ diff --git a/tests/src/test_engine.cpp b/tests/src/test_engine.cpp index 9ebc1171eebd2256c1f87887f8381b3c8790ad22..f8a7baf5c5e313fd405ed9ca22895433af8bfaad 100644 --- a/tests/src/test_engine.cpp +++ b/tests/src/test_engine.cpp @@ -41,9 +41,7 @@ #include "trompeloeil.hpp" -SUPPRESS_WARNINGS_BEGIN #include "mocks/openvr_mock.hpp" -SUPPRESS_WARNINGS_END #include "mocks/opengl_mock.hpp" #include "mocks/sdl_mock.hpp" diff --git a/tests/src/test_openvr_controller_system.cpp b/tests/src/test_openvr_controller_system.cpp new file mode 100644 index 0000000000000000000000000000000000000000..225ea9b498109211629d506451deb94b6834fde4 --- /dev/null +++ b/tests/src/test_openvr_controller_system.cpp @@ -0,0 +1,118 @@ +//------------------------------------------------------------------------------ +// 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" + +SUPPRESS_WARNINGS_BEGIN +#include "GL/glew.h" +SUPPRESS_WARNINGS_END + +#include "trompeloeil.hpp" + +#include "phx/display_system_openvr.hpp" +#include "phx/entity.hpp" +#include "phx/material_handle.hpp" +#include "phx/mesh_handle.hpp" +#include "phx/openvr_controller_behavior.hpp" +#include "phx/openvr_controller_system.hpp" +#include "phx/rendering_system.hpp" +#include "phx/scene.hpp" + +#include "mocks/opengl_mock.hpp" +#include "mocks/openvr_mock.hpp" +#include "mocks/sdl_mock.hpp" + +using trompeloeil::_; +using trompeloeil::ne; + +extern template struct trompeloeil::reporter<trompeloeil::specialized>; + +class EngineStopTestSystem : public phx::System { + public: + explicit EngineStopTestSystem(phx::Engine* engine, int stop_in_frame = 1) + : System(engine), stop_on_update_(stop_in_frame) {} + + void Update(const phx::FrameTimer::TimeInfo& time_info) override { + update_counter_++; + if (update_counter_ >= stop_on_update_) { + GetEngine()->Stop(); + } + time_info_ = &time_info; + } + + const phx::FrameTimer::TimeInfo* time_info_ = nullptr; + + private: + int update_counter_ = 0; + int stop_on_update_ = 1; +}; + +SCENARIO( + "The OpenVRControllerSystem automatically inserts controller entities into " + "a scene if controllers are connected", + "[phx][phx::OpenVRControllerSystem]") { + 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>(); + + GIVEN( + "The display system has an HMD and the engine has a scene. Also, there's " + "an OpenVRControllerSystem.") { + display_system->CreateHMD(); + auto scene = std::make_shared<phx::Scene>(); + engine.SetScene(scene); + engine.CreateSystem<phx::OpenVRControllerSystem>(display_system); + engine.CreateSystem<EngineStopTestSystem>(); + engine.CreateSystem<phx::RenderingSystem>(display_system); + + WHEN("We run the engine for once frame (updating each system once)") { + engine.Run(); + + THEN( + "There are controller entities in the scene with controller " + "behaviors attached to them, as well as mesh and material handles, " + "and the controllers are left and right") { + auto controller_entities = + scene->GetEntitiesWithComponents<phx::OpenVRControllerBehavior>(); + REQUIRE(controller_entities.size() == 2); + + for (auto entity : controller_entities) { + REQUIRE(entity->GetFirstComponent<phx::MeshHandle>() != nullptr); + REQUIRE(entity->GetFirstComponent<phx::MaterialHandle>() != nullptr); + } + + auto side0 = controller_entities[0] + ->GetFirstComponent<phx::OpenVRControllerBehavior>() + ->GetSide(); + auto side1 = controller_entities[1] + ->GetFirstComponent<phx::OpenVRControllerBehavior>() + ->GetSide(); + REQUIRE(side0 != side1); + } + } + } +}