From 261f8f976fdb362a719b8a66371285bb1c878f3f Mon Sep 17 00:00:00 2001 From: janhsimon Date: Sat, 31 Dec 2022 17:57:05 +0100 Subject: [PATCH] Add fly mode using select controller button --- src/Controllers.cpp | 111 +++++++++++++++++++++++++------------------- src/Controllers.h | 15 ++++-- src/Main.cpp | 29 ++++++++++-- src/Pipeline.cpp | 1 + src/Pipeline.h | 4 +- src/Renderer.cpp | 12 ++--- src/Renderer.h | 4 +- src/Util.cpp | 54 +++++++++++++++++++++ src/Util.h | 30 ++++++++---- 9 files changed, 186 insertions(+), 74 deletions(-) diff --git a/src/Controllers.cpp b/src/Controllers.cpp index 17cecfa..9613a89 100644 --- a/src/Controllers.cpp +++ b/src/Controllers.cpp @@ -14,8 +14,6 @@ constexpr size_t controllerCount = 2u; const std::string actionSetName = "actionset"; const std::string localizedActionSetName = "Actions"; -const std::string actionName = "handpose"; -const std::string localizedActionName = "Hand Pose"; } // namespace Controllers::Controllers(XrInstance instance, XrSession session) : session(session) @@ -40,20 +38,19 @@ Controllers::Controllers(XrInstance instance, XrSession session) : session(sessi paths.at(0u) = util::stringToPath(instance, "/user/hand/left"); paths.at(1u) = util::stringToPath(instance, "/user/hand/right"); - // Create action - XrActionCreateInfo actionCreateInfo{ XR_TYPE_ACTION_CREATE_INFO }; - actionCreateInfo.actionType = XR_ACTION_TYPE_POSE_INPUT; - actionCreateInfo.countSubactionPaths = static_cast(paths.size()); - actionCreateInfo.subactionPaths = paths.data(); - - memcpy(actionCreateInfo.actionName, actionName.data(), actionName.length() + 1u); - memcpy(actionCreateInfo.localizedActionName, localizedActionName.data(), localizedActionName.length() + 1u); + // Create actions + if (!util::createAction(actionSet, paths, "handpose", "Hand Pose", XR_ACTION_TYPE_POSE_INPUT, poseAction)) + { + util::error(Error::GenericOpenXR); + valid = false; + return; + } - result = xrCreateAction(actionSet, &actionCreateInfo, &action); - if (XR_FAILED(result)) + if (!util::createAction(actionSet, paths, "fly", "Fly", XR_ACTION_TYPE_FLOAT_INPUT, flyAction)) { util::error(Error::GenericOpenXR); valid = false; + return; } // Create spaces @@ -63,7 +60,7 @@ Controllers::Controllers(XrInstance instance, XrSession session) : session(sessi const XrPath& path = paths.at(controllerIndex); XrActionSpaceCreateInfo actionSpaceCreateInfo{ XR_TYPE_ACTION_SPACE_CREATE_INFO }; - actionSpaceCreateInfo.action = action; + actionSpaceCreateInfo.action = poseAction; actionSpaceCreateInfo.poseInActionSpace = util::makeIdentity(); actionSpaceCreateInfo.subactionPath = path; @@ -77,9 +74,11 @@ Controllers::Controllers(XrInstance instance, XrSession session) : session(sessi } // Suggest simple controller binding (generic) - const std::array bindings = { - { { action, util::stringToPath(instance, "/user/hand/left/input/aim/pose") }, - { action, util::stringToPath(instance, "/user/hand/right/input/aim/pose") } } + const std::array bindings = { + { { poseAction, util::stringToPath(instance, "/user/hand/left/input/aim/pose") }, + { poseAction, util::stringToPath(instance, "/user/hand/right/input/aim/pose") }, + { flyAction, util::stringToPath(instance, "/user/hand/left/input/select/click") }, + { flyAction, util::stringToPath(instance, "/user/hand/right/input/select/click") } } }; XrInteractionProfileSuggestedBinding interactionProfileSuggestedBinding{ @@ -111,7 +110,8 @@ Controllers::Controllers(XrInstance instance, XrSession session) : session(sessi return; } - transforms.resize(controllerCount); + poses.resize(controllerCount); + flySpeeds.resize(controllerCount); } Controllers::~Controllers() @@ -121,9 +121,20 @@ Controllers::~Controllers() xrDestroySpace(space); } - xrDestroyAction(action); + if (flyAction) + { + xrDestroyAction(flyAction); + } - xrDestroyActionSet(actionSet); + if (poseAction) + { + xrDestroyAction(poseAction); + } + + if (actionSet) + { + xrDestroyActionSet(actionSet); + } } bool Controllers::sync(XrSpace space, XrTime time) @@ -144,52 +155,51 @@ bool Controllers::sync(XrSpace space, XrTime time) return false; } - // Update the transforms + // Update the actions for (size_t controllerIndex = 0u; controllerIndex < controllerCount; ++controllerIndex) { const XrPath& path = paths.at(controllerIndex); - XrActionStatePose actionStatePose{ XR_TYPE_ACTION_STATE_POSE }; - - XrActionStateGetInfo actionStateGetInfo{ XR_TYPE_ACTION_STATE_GET_INFO }; - actionStateGetInfo.action = action; - actionStateGetInfo.subactionPath = path; - - result = xrGetActionStatePose(session, &actionStateGetInfo, &actionStatePose); - if (XR_FAILED(result)) + // Pose + XrActionStatePose poseState{ XR_TYPE_ACTION_STATE_POSE }; + if (!util::updateActionStatePose(session, poseAction, path, poseState)) { util::error(Error::GenericOpenXR); return false; } - if (!actionStatePose.isActive) + if (poseState.isActive) { - continue; + XrSpaceLocation spaceLocation{ XR_TYPE_SPACE_LOCATION }; + result = xrLocateSpace(spaces.at(controllerIndex), space, time, &spaceLocation); + if (XR_FAILED(result)) + { + util::error(Error::GenericOpenXR); + return false; + } + + // Check that the position and orientation are valid and tracked + constexpr XrSpaceLocationFlags checkFlags = + XR_SPACE_LOCATION_POSITION_VALID_BIT | XR_SPACE_LOCATION_POSITION_TRACKED_BIT | + XR_SPACE_LOCATION_ORIENTATION_VALID_BIT | XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT; + if ((spaceLocation.locationFlags & checkFlags) == checkFlags) + { + poses.at(controllerIndex) = util::poseToMatrix(spaceLocation.pose); + } } - XrSpaceLocation spaceLocation{ XR_TYPE_SPACE_LOCATION }; - - result = xrLocateSpace(spaces.at(controllerIndex), space, time, &spaceLocation); - if (XR_FAILED(result)) + // Fly speed + XrActionStateFloat flySpeedState{ XR_TYPE_ACTION_STATE_FLOAT }; + if (!util::updateActionStateFloat(session, flyAction, path, flySpeedState)) { util::error(Error::GenericOpenXR); return false; } - const bool positionValid = (spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0; - const bool positionTracking = (spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) != 0; - - const bool orientationValid = (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0; - const bool orientationTracking = (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) != 0; - - const bool active = positionValid && positionTracking && orientationValid && orientationTracking; - - if (!active) + if (flySpeedState.isActive) { - continue; + flySpeeds.at(controllerIndex) = flySpeedState.currentState; } - - transforms.at(controllerIndex) = util::poseToMatrix(spaceLocation.pose); } return true; @@ -200,7 +210,12 @@ bool Controllers::isValid() const return valid; } -glm::mat4 Controllers::getTransform(size_t controllerIndex) const +glm::mat4 Controllers::getPose(size_t controllerIndex) const +{ + return poses.at(controllerIndex); +} + +float Controllers::getFlySpeed(size_t controllerIndex) const { - return transforms.at(controllerIndex); + return flySpeeds.at(controllerIndex); } diff --git a/src/Controllers.h b/src/Controllers.h index 4da7982..8ca470c 100644 --- a/src/Controllers.h +++ b/src/Controllers.h @@ -9,7 +9,8 @@ /* * The controllers class handles OpenXR controller support. It represents the controller system as a whole, not an * individual controller. This is more convenient due to the OpenXR API. It allows the application to retrieve the - * current transform of a controller, which is then used to accuretely pose the hand models in the scene. + * current pose of a controller, which is then used to accurately pose the hand models in the scene. It also exposes the + * current fly speed, which is used to fly the camera in the direction of the controller. */ class Controllers final { @@ -21,15 +22,19 @@ class Controllers final bool isValid() const; - glm::mat4 getTransform(size_t controllerIndex) const; + glm::mat4 getPose(size_t controllerIndex) const; + float getFlySpeed(size_t controllerIndex) const; private: bool valid = true; XrSession session = nullptr; std::vector paths; - XrActionSet actionSet = nullptr; - XrAction action = nullptr; std::vector spaces; - std::vector transforms; + + std::vector poses; + std::vector flySpeeds; + + XrActionSet actionSet = nullptr; + XrAction poseAction = nullptr, flyAction = nullptr; }; \ No newline at end of file diff --git a/src/Main.cpp b/src/Main.cpp index e6ce735..2c028cf 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -10,8 +10,15 @@ #include +namespace +{ +constexpr float flySpeedMultiplier = 2.5f; +} + int main() { + glm::mat4 cameraMatrix = glm::mat4(1.0f); // Transform from world to stage space + Context context; if (!context.isValid()) { @@ -130,18 +137,30 @@ int main() return EXIT_FAILURE; } - // Update static float time = 0.0f; time += deltaTime; + // Update + for (size_t controllerIndex = 0u; controllerIndex < 2u; ++controllerIndex) + { + const float flySpeed = controllers.getFlySpeed(controllerIndex); + if (flySpeed > 0.0f) + { + const glm::vec3 forward = glm::normalize(controllers.getPose(controllerIndex)[2]); + cameraMatrix = glm::translate(cameraMatrix, forward * flySpeed * flySpeedMultiplier * deltaTime); + } + } + + const glm::mat4 inverseCameraMatrix = glm::inverse(cameraMatrix); + handModelLeft.worldMatrix = inverseCameraMatrix * controllers.getPose(0u); + handModelRight.worldMatrix = inverseCameraMatrix * controllers.getPose(1u); + handModelRight.worldMatrix = glm::scale(handModelRight.worldMatrix, { -1.0f, 1.0f, 1.0f }); + bikeModel.worldMatrix = glm::rotate(glm::translate(glm::mat4(1.0f), { 0.5f, 0.0f, -4.5f }), time * 0.2f, { 0.0f, 1.0f, 0.0f }); - handModelLeft.worldMatrix = controllers.getTransform(0u); - handModelRight.worldMatrix = glm::scale(controllers.getTransform(1u), { -1.0f, 1.0f, 1.0f }); - // Render - renderer.render(swapchainImageIndex, time); + renderer.render(cameraMatrix, swapchainImageIndex, time); const MirrorView::RenderResult mirrorResult = mirrorView.render(swapchainImageIndex); if (mirrorResult == MirrorView::RenderResult::Error) diff --git a/src/Pipeline.cpp b/src/Pipeline.cpp index 9179756..79cfa33 100644 --- a/src/Pipeline.cpp +++ b/src/Pipeline.cpp @@ -1,5 +1,6 @@ #include "Pipeline.h" +#include "Context.h" #include "Util.h" #include diff --git a/src/Pipeline.h b/src/Pipeline.h index 02c6fea..e2c22fe 100644 --- a/src/Pipeline.h +++ b/src/Pipeline.h @@ -1,10 +1,12 @@ #pragma once -#include "Context.h" +#include #include #include +class Context; + /* * The pipeline class wraps a Vulkan pipeline for convenience. It describes the rendering technique to use, including * shaders, culling, scissoring, and other aspects. diff --git a/src/Renderer.cpp b/src/Renderer.cpp index 93f81f9..0b88155 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -23,7 +23,6 @@ Renderer::Renderer(const Context* context, const std::vector& models) : context(context), headset(headset), models(models) { - const VkPhysicalDevice vkPhysicalDevice = context->getVkPhysicalDevice(); const VkDevice device = context->getVkDevice(); // Create a command pool @@ -238,7 +237,7 @@ Renderer::~Renderer() } } -void Renderer::render(size_t swapchainImageIndex, float time) +void Renderer::render(const glm::mat4& cameraMatrix, size_t swapchainImageIndex, float time) { currentRenderProcessIndex = (currentRenderProcessIndex + 1u) % renderProcesses.size(); @@ -273,7 +272,7 @@ void Renderer::render(size_t swapchainImageIndex, float time) for (size_t eyeIndex = 0u; eyeIndex < headset->getEyeCount(); ++eyeIndex) { renderProcess->staticVertexUniformData.viewProjectionMatrices.at(eyeIndex) = - headset->getEyeProjectionMatrix(eyeIndex) * headset->getEyeViewMatrix(eyeIndex); + headset->getEyeProjectionMatrix(eyeIndex) * headset->getEyeViewMatrix(eyeIndex) * cameraMatrix; } renderProcess->staticFragmentUniformData.time = time; @@ -324,9 +323,10 @@ void Renderer::render(size_t swapchainImageIndex, float time) const Model* model = models.at(modelIndex); // Bind the uniform buffer - const uint32_t uniformBufferOffset = static_cast( - util::align(sizeof(RenderProcess::DynamicVertexUniformData), context->getUniformBufferOffsetAlignment()) * - static_cast(modelIndex)); + const uint32_t uniformBufferOffset = + static_cast(util::align(static_cast(sizeof(RenderProcess::DynamicVertexUniformData)), + context->getUniformBufferOffsetAlignment()) * + static_cast(modelIndex)); vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0u, 1u, &descriptorSet, 1u, &uniformBufferOffset); diff --git a/src/Renderer.h b/src/Renderer.h index db1dbde..90bdfbb 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include @@ -24,7 +26,7 @@ class Renderer final Renderer(const Context* context, const Headset* headset, const MeshData* meshData, const std::vector& models); ~Renderer(); - void render(size_t swapchainImageIndex, float time); + void render(const glm::mat4& cameraMatrix, size_t swapchainImageIndex, float time); void submit(bool useSemaphores) const; bool isValid() const; diff --git a/src/Util.cpp b/src/Util.cpp index 94c38ff..da5db09 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -162,6 +162,30 @@ XrPath util::stringToPath(XrInstance instance, const std::string& string) return path; } +bool util::createAction(XrActionSet actionSet, + const std::vector& paths, + const std::string& actionName, + const std::string& localizedActionName, + XrActionType type, + XrAction& action) +{ + XrActionCreateInfo actionCreateInfo{ XR_TYPE_ACTION_CREATE_INFO }; + actionCreateInfo.actionType = type; + actionCreateInfo.countSubactionPaths = static_cast(paths.size()); + actionCreateInfo.subactionPaths = paths.data(); + + memcpy(actionCreateInfo.actionName, actionName.data(), actionName.length() + 1u); + memcpy(actionCreateInfo.localizedActionName, localizedActionName.data(), localizedActionName.length() + 1u); + + XrResult result = xrCreateAction(actionSet, &actionCreateInfo, &action); + if (XR_FAILED(result)) + { + return false; + } + + return true; +} + XrPosef util::makeIdentity() { XrPosef identity; @@ -197,4 +221,34 @@ glm::mat4 util::createProjectionMatrix(XrFovf fov, float nearClip, float farClip projectionMatrix[2] = { (r + l) / w, (u + d) / h, -(farClip + nearClip) / (farClip - nearClip), -1.0f }; projectionMatrix[3] = { 0.0f, 0.0f, -(farClip * (nearClip + nearClip)) / (farClip - nearClip), 0.0f }; return projectionMatrix; +} + +bool util::updateActionStatePose(XrSession session, XrAction action, XrPath path, XrActionStatePose& state) +{ + XrActionStateGetInfo actionStateGetInfo{ XR_TYPE_ACTION_STATE_GET_INFO }; + actionStateGetInfo.action = action; + actionStateGetInfo.subactionPath = path; + + const XrResult result = xrGetActionStatePose(session, &actionStateGetInfo, &state); + if (XR_FAILED(result)) + { + return false; + } + + return true; +} + +bool util::updateActionStateFloat(XrSession session, XrAction action, XrPath path, XrActionStateFloat& state) +{ + XrActionStateGetInfo actionStateGetInfo{ XR_TYPE_ACTION_STATE_GET_INFO }; + actionStateGetInfo.action = action; + actionStateGetInfo.subactionPath = path; + + const XrResult result = xrGetActionStateFloat(session, &actionStateGetInfo, &state); + if (XR_FAILED(result)) + { + return false; + } + + return true; } \ No newline at end of file diff --git a/src/Util.h b/src/Util.h index 2212c5e..7e1f867 100644 --- a/src/Util.h +++ b/src/Util.h @@ -38,29 +38,43 @@ bool loadXrExtensionFunction(XrInstance instance, const std::string& name, PFN_x // Loads a Vulkan extension function by 'name', returns nullptr on error PFN_vkVoidFunction loadVkExtensionFunction(VkInstance instance, const std::string& name); -// Unpacks an extension list in a single string into a vector of c-style strings +// Unpacks a Vulkan or OpenXR extension list in a single string into a vector of c-style strings std::vector unpackExtensionString(const std::string& string); -// Loads a shader from 'file' into 'shaderModule', returns false on error +// Loads a Vulkan shader from 'file' into 'shaderModule', returns false on error bool loadShaderFromFile(VkDevice device, const std::string& filename, VkShaderModule& shaderModule); -// Finds a suitable memory type index for given requirements and properties, returns false on error +// Finds a suitable Vulkan memory type index for given requirements and properties, returns false on error bool findSuitableMemoryTypeIndex(VkPhysicalDevice physicalDevice, VkMemoryRequirements requirements, VkMemoryPropertyFlags properties, uint32_t& typeIndex); // Aligns a value to an alignment -size_t align(size_t value, VkDeviceSize alignment); +VkDeviceSize align(VkDeviceSize value, VkDeviceSize alignment); -// Creates a path from a name string +// Creates an OpenXR path from a name string XrPath stringToPath(XrInstance instance, const std::string& string); -// Creates an identity pose +// Creates an OpenXR action with a given names, returns false on error +bool createAction(XrActionSet actionSet, + const std::vector& paths, + const std::string& actionName, + const std::string& localizedActionName, + XrActionType type, + XrAction& action); + +// Creates an OpenXR identity pose XrPosef makeIdentity(); -// Converts a pose to a transformation matrix +// Converts an OpenXR pose to a transformation matrix glm::mat4 poseToMatrix(const XrPosef& pose); -// Creates a projection matrix +// Creates an OpenXR projection matrix glm::mat4 createProjectionMatrix(XrFovf fov, float nearClip, float farClip); + +// Updates an action state for a given action and path in pose format, returns false on error +bool updateActionStatePose(XrSession session, XrAction action, XrPath path, XrActionStatePose& state); + +// Updates an action state for a given action and path in float format, returns false on error +bool updateActionStateFloat(XrSession session, XrAction action, XrPath path, XrActionStateFloat& state); } // namespace util \ No newline at end of file