|
| 1 | + |
| 2 | +#include "game.h" |
| 3 | + |
| 4 | +#include <vsgvr/app/CompositionLayerProjection.h> |
| 5 | +#include <vsgvr/app/CompositionLayerQuad.h> |
| 6 | + |
| 7 | +#include "../../models/controller/controller.cpp" |
| 8 | +#include "../../models/controller/controller2.cpp" |
| 9 | +#include "../../models/world/world.cpp" |
| 10 | +#include "../../models/assets/teleport_marker/teleport_marker.cpp" |
| 11 | + |
| 12 | +#include "interactions/interaction_teleport.h" |
| 13 | + |
| 14 | +Game::Game(vsg::ref_ptr<vsgvr::Instance> xrInstance, vsg::ref_ptr<vsgvr::Viewer> vr, vsg::ref_ptr<vsg::Viewer> desktopViewer, bool displayDesktopWindow) |
| 15 | + : _xrInstance(xrInstance) |
| 16 | + , _vr(vr) |
| 17 | + , _desktopViewer(desktopViewer) |
| 18 | + , _desktopWindowEnabled(displayDesktopWindow) |
| 19 | +{ |
| 20 | + loadScene(); |
| 21 | + initVR(); |
| 22 | + initActions(); |
| 23 | + _lastFrameTime = vsg::clock::now(); |
| 24 | +} |
| 25 | + |
| 26 | +Game::~Game() {} |
| 27 | + |
| 28 | +void Game::loadScene() |
| 29 | +{ |
| 30 | + // User / OpenXR root space |
| 31 | + _sceneRoot = vsg::Group::create(); |
| 32 | + _controllerLeft = controller(); |
| 33 | + _sceneRoot->addChild(_controllerLeft); |
| 34 | + _controllerRight = controller2(); |
| 35 | + _sceneRoot->addChild(_controllerRight); |
| 36 | + |
| 37 | + _teleportMarker = vsg::Switch::create(); |
| 38 | + auto teleportMatrix = vsg::MatrixTransform::create(); |
| 39 | + _teleportMarker->addChild(false, teleportMatrix); |
| 40 | + teleportMatrix->addChild(teleport_marker()); |
| 41 | + _sceneRoot->addChild(_teleportMarker); |
| 42 | + |
| 43 | + // User origin - The regular vsg scene / world space, |
| 44 | + // which the user origin may move around in |
| 45 | + _userOrigin = vsgvr::UserOrigin::create(); |
| 46 | + _sceneRoot->addChild(_userOrigin); |
| 47 | + |
| 48 | + _ground = world(); // TODO: Not actually correct - buildings etc in the world should not be 'ground' |
| 49 | + _userOrigin->addChild(_ground); |
| 50 | +} |
| 51 | + |
| 52 | +void Game::initVR() |
| 53 | +{ |
| 54 | + // Create CommandGraphs to render the scene to the HMD |
| 55 | + // TODO: This only really exists because vsg::createCommandGraphForView requires |
| 56 | + // a Window instance. Other than some possible improvements later, it could use the same code as vsg |
| 57 | + // OpenXR rendering may use one or more command graphs, as decided by the viewer |
| 58 | + // (TODO: At the moment only a single CommandGraph will be used, even if there's multiple XR views) |
| 59 | + // Note: assignHeadlight = false -> Scene lighting is required |
| 60 | + auto headsetCompositionLayer = vsgvr::CompositionLayerProjection::create(_vr->getInstance(), _vr->getTraits(), _vr->getSession()->getSpace()); |
| 61 | + auto xrCommandGraphs = headsetCompositionLayer->createCommandGraphsForView(_vr->getSession(), _sceneRoot, _xrCameras, false); |
| 62 | + // TODO: This is almost identical to Viewer::assignRecordAndSubmitTaskAndPresentation - The only difference is |
| 63 | + // that OpenXRViewer doesn't have presentation - If presentation was abstracted we could avoid awkward duplication here |
| 64 | + headsetCompositionLayer->assignRecordAndSubmitTask(xrCommandGraphs); |
| 65 | + headsetCompositionLayer->compile(); |
| 66 | + _vr->compositionLayers.push_back(headsetCompositionLayer); |
| 67 | + |
| 68 | + // TODO: Quick hack to render <something> to a CompositionLayerQuad - This should do something better |
| 69 | + { |
| 70 | + auto lookAt = vsg::LookAt::create(vsg::dvec3(0.0, 0.0, 10.0), vsg::dvec3(0.0, 0.0, 0.0), vsg::dvec3(0.0, -1.0, 0.0)); |
| 71 | + // Camera parameters as if it's rendering to a desktop display, appropriate size for the displayed quad |
| 72 | + auto perspective = vsg::Perspective::create(30.0, 1920.0 / 1080.0, 0.1, 100.0); |
| 73 | + |
| 74 | + // Configure rendering from an overhead camera, displaying the scene |
| 75 | + // - A camera is not require here, but is required if the application later wants a reference to it |
| 76 | + // - The composition layer's basic parameters (pose, scale) may be modified later at any time |
| 77 | + // - There is a runtime-specific limit to the number of composition layers. Only a few should be used, if more than one. |
| 78 | + auto overheadCamera = vsg::Camera::create(perspective, lookAt, vsg::ViewportState::create(0, 0, 1920, 1080)); |
| 79 | + |
| 80 | + // A quad positioned in the world (scene reference space) |
| 81 | + // auto quadLayer = vsgvr::CompositionLayerQuad::create(_vr->getInstance(), _vr->getTraits(), _vr->getSession()->getSpace(), 1920, 1080); |
| 82 | + // quadLayer->pose.position = { 0.0, 1.0, -4.0 }; |
| 83 | + |
| 84 | + // A quad positioned in front of the user's face |
| 85 | + auto faceLockedSpace = vsgvr::ReferenceSpace::create(_vr->getSession()->getSession(), XrReferenceSpaceType::XR_REFERENCE_SPACE_TYPE_VIEW); |
| 86 | + auto quadLayer = vsgvr::CompositionLayerQuad::create(_vr->getInstance(), _vr->getTraits(), faceLockedSpace, 1920, 1080); |
| 87 | + quadLayer->pose.position = { 0.0, 0.0, -4.0 }; |
| 88 | + |
| 89 | + /*auto rot = vsg::quat({0.0f, 5.0f, 2.5f}, {0.0f, 0.0f, 2.5f}); |
| 90 | + quadLayer->pose.orientation = { |
| 91 | + rot.x, rot.y, rot.z, rot.w |
| 92 | + };*/ |
| 93 | + // Quad size taking in to account aspect ratio |
| 94 | + quadLayer->sizeMeters = { 1.920f, 1.080f }; |
| 95 | + |
| 96 | + std::vector<vsg::ref_ptr<vsg::Camera>> cameras = { overheadCamera }; |
| 97 | + auto overheadCommandGraphs = quadLayer->createCommandGraphsForView(_vr->getSession(), _sceneRoot, cameras, false); |
| 98 | + quadLayer->assignRecordAndSubmitTask(overheadCommandGraphs); |
| 99 | + quadLayer->compile(); |
| 100 | + _vr->compositionLayers.push_back(quadLayer); |
| 101 | + } |
| 102 | + |
| 103 | + if(_desktopWindowEnabled) |
| 104 | + { |
| 105 | + // Create a CommandGraph to render the desktop window |
| 106 | + auto lookAt = vsg::LookAt::create(vsg::dvec3(-4.0, -15.0, 25.0), vsg::dvec3(0.0, 0.0, 0.0), vsg::dvec3(0.0, 0.0, 1.0)); |
| 107 | + auto desktopWindow = _desktopViewer->windows().front(); |
| 108 | + auto perspective = vsg::Perspective::create(30.0, |
| 109 | + static_cast<double>(desktopWindow->extent2D().width) / static_cast<double>(desktopWindow->extent2D().height) |
| 110 | + , 0.1, 100.0 |
| 111 | + ); |
| 112 | + _desktopCamera = vsg::Camera::create(perspective, lookAt, vsg::ViewportState::create(desktopWindow->extent2D())); |
| 113 | + auto desktopCommandGraph = vsg::createCommandGraphForView(desktopWindow, _desktopCamera, _sceneRoot, VK_SUBPASS_CONTENTS_INLINE, false); |
| 114 | + _desktopViewer->assignRecordAndSubmitTaskAndPresentation({ desktopCommandGraph }); |
| 115 | + _desktopViewer->compile(); |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +void Game::initActions() |
| 120 | +{ |
| 121 | + // Tracking the location of the user's headset is achieved by tracking the VIEW reference space |
| 122 | + // This may be done by the application, but vsgvr::Viewer can also track a set of SpaceBindings per-frame (within the session's reference space) |
| 123 | + _headPose = vsgvr::SpaceBinding::create(vsgvr::ReferenceSpace::create(_vr->getSession()->getSession(), XrReferenceSpaceType::XR_REFERENCE_SPACE_TYPE_VIEW)); |
| 124 | + _vr->spaceBindings.push_back(_headPose); |
| 125 | + |
| 126 | + // Input devices are tracked via ActionPoseBindings - Tracking elements from the OpenXR device tree in the session space, |
| 127 | + // along with binding the OpenXR input subsystem through to usable actions. |
| 128 | + _baseActionSet = vsgvr::ActionSet::create(_xrInstance, "controller_positions", "Controller Positions"); |
| 129 | + // Pose bindings |
| 130 | + _leftHandPose = vsgvr::ActionPoseBinding::create(_xrInstance, _baseActionSet, "left_hand", "Left Hand"); |
| 131 | + _rightHandPose = vsgvr::ActionPoseBinding::create(_xrInstance, _baseActionSet, "right_hand", "Right Hand"); |
| 132 | + |
| 133 | + _baseActionSet->actions = { |
| 134 | + _leftHandPose, |
| 135 | + _rightHandPose, |
| 136 | + }; |
| 137 | + |
| 138 | + _interactions.emplace("teleport", new Interaction_teleport(_xrInstance, _headPose, _leftHandPose, _teleportMarker, _ground)); |
| 139 | + |
| 140 | + // Ask OpenXR to suggest interaction bindings. |
| 141 | + // * If subpaths are used, list all paths that each action should be bound for |
| 142 | + // * Note that this may only be called once for each interaction profile (but may be across multiple overlapping action sets) |
| 143 | + // * If a particular profile is used, all interactions should be bound to this i.e. If grabbing items only specifies bindings for |
| 144 | + // an oculus controller, it will not be bound if the simple_controller is chosen by the runtime |
| 145 | + std::map<std::string, std::list<vsgvr::ActionSet::SuggestedInteractionBinding>> actionsToSuggest; |
| 146 | + actionsToSuggest["/interaction_profiles/khr/simple_controller"] = { |
| 147 | + {_leftHandPose, "/user/hand/left/input/aim/pose"}, |
| 148 | + {_rightHandPose, "/user/hand/right/input/aim/pose"}, |
| 149 | + }; |
| 150 | + actionsToSuggest["/interaction_profiles/oculus/touch_controller"] = { |
| 151 | + {_leftHandPose, "/user/hand/left/input/aim/pose"}, |
| 152 | + {_rightHandPose, "/user/hand/right/input/aim/pose"}, |
| 153 | + }; |
| 154 | + for(auto& interaction : _interactions ) |
| 155 | + { |
| 156 | + for (auto& p : interaction.second->actionsToSuggest()) |
| 157 | + { |
| 158 | + for( auto& x : p.second ) |
| 159 | + { |
| 160 | + actionsToSuggest[p.first].push_back(x); |
| 161 | + } |
| 162 | + } |
| 163 | + } |
| 164 | + for (auto& p : actionsToSuggest) |
| 165 | + { |
| 166 | + if (! vsgvr::ActionSet::suggestInteractionBindings(_xrInstance, p.first, p.second)) |
| 167 | + { |
| 168 | + throw vsg::Exception({ "Failed to configure interaction bindings for controllers" }); |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + // All action sets the viewer should attach to the session |
| 173 | + _vr->actionSets.push_back(_baseActionSet); |
| 174 | + for( auto& interaction : _interactions ) |
| 175 | + { |
| 176 | + _vr->actionSets.push_back(interaction.second->actionSet()); |
| 177 | + } |
| 178 | + |
| 179 | + // The action sets which are currently active (will be synced each frame) |
| 180 | + _vr->activeActionSets.push_back(_baseActionSet); |
| 181 | + |
| 182 | + // TODO: Set up input action to switch between modes |
| 183 | + // TODO: Display active mode somewhere in the world, maybe as text panel when looking at controllers |
| 184 | + _vr->activeActionSets.push_back(_interactions["teleport"]->actionSet()); |
| 185 | + |
| 186 | + // add close handler to respond the close window button and pressing escape |
| 187 | + _desktopViewer->addEventHandler(vsg::CloseHandler::create(_desktopViewer)); |
| 188 | +} |
| 189 | + |
| 190 | +void Game::frame() |
| 191 | +{ |
| 192 | + // OpenXR events must be checked first |
| 193 | + auto pol = _vr->pollEvents(); |
| 194 | + if (pol == vsgvr::Viewer::PollEventsResult::Exit) |
| 195 | + { |
| 196 | + // User exited through VR overlay / XR runtime |
| 197 | + shouldExit = true; |
| 198 | + return; |
| 199 | + } |
| 200 | + |
| 201 | + if (pol == vsgvr::Viewer::PollEventsResult::NotRunning) |
| 202 | + { |
| 203 | + return; |
| 204 | + } |
| 205 | + else if (pol == vsgvr::Viewer::PollEventsResult::RuntimeIdle) |
| 206 | + { |
| 207 | + // Reduce power usage, wait for XR to wake |
| 208 | + std::this_thread::sleep_for(std::chrono::milliseconds(100)); |
| 209 | + return; |
| 210 | + } |
| 211 | + |
| 212 | + // Scene graph updates |
| 213 | + if (_leftHandPose->getTransformValid()) |
| 214 | + { |
| 215 | + _controllerLeft->matrix = _leftHandPose->getTransform(); |
| 216 | + } |
| 217 | + if (_rightHandPose->getTransformValid()) |
| 218 | + { |
| 219 | + _controllerRight->matrix = _rightHandPose->getTransform(); |
| 220 | + } |
| 221 | + |
| 222 | + // The session is running in some form, and a frame must be processed |
| 223 | + // The OpenXR frame loop takes priority - Acquire a frame to render into |
| 224 | + auto shouldQuit = false; |
| 225 | + |
| 226 | + if(_desktopWindowEnabled) |
| 227 | + { |
| 228 | + // Match the desktop camera to the HMD view |
| 229 | + _desktopCamera->viewMatrix = _xrCameras.front()->viewMatrix; |
| 230 | + _desktopCamera->projectionMatrix = _xrCameras.front()->projectionMatrix; |
| 231 | + |
| 232 | + // Desktop render |
| 233 | + // * The scene graph is updated by the desktop render |
| 234 | + // * if PollEventsResult::RunningDontRender the desktop render could be skipped |
| 235 | + if (_desktopViewer->advanceToNextFrame()) |
| 236 | + { |
| 237 | + _desktopViewer->handleEvents(); |
| 238 | + _desktopViewer->update(); |
| 239 | + _desktopViewer->recordAndSubmit(); |
| 240 | + _desktopViewer->present(); |
| 241 | + } |
| 242 | + else |
| 243 | + { |
| 244 | + // Desktop window was closed |
| 245 | + shouldQuit = true; |
| 246 | + return; |
| 247 | + } |
| 248 | + } |
| 249 | + |
| 250 | + if (_vr->advanceToNextFrame()) |
| 251 | + { |
| 252 | + if (pol == vsgvr::Viewer::PollEventsResult::RunningDontRender) |
| 253 | + { |
| 254 | + // XR Runtime requested that rendering is not performed (not visible to user) |
| 255 | + // While this happens frames must still be acquired and released however, in |
| 256 | + // order to synchronise with the OpenXR runtime |
| 257 | + } |
| 258 | + else |
| 259 | + { |
| 260 | + for (auto& interaction : _interactions) |
| 261 | + { |
| 262 | + if (std::find(_vr->activeActionSets.begin(), _vr->activeActionSets.end(), |
| 263 | + interaction.second->actionSet()) != _vr->activeActionSets.end()) |
| 264 | + { |
| 265 | + auto deltaT = static_cast<double>( |
| 266 | + std::chrono::duration_cast<std::chrono::microseconds>(_vr->getFrameStamp()->time - _lastFrameTime).count() |
| 267 | + ) / 1e6; |
| 268 | + _lastFrameTime = _vr->getFrameStamp()->time; |
| 269 | + interaction.second->frame(_userOrigin, *this, deltaT); |
| 270 | + } |
| 271 | + } |
| 272 | + |
| 273 | + // Render to the HMD |
| 274 | + _vr->recordAndSubmit(); // Render XR frame |
| 275 | + } |
| 276 | + } |
| 277 | + |
| 278 | + // End the frame, and present to user |
| 279 | + // Frames must be explicitly released, even if the previous advanceToNextFrame returned false (PollEventsResult::RunningDontRender) |
| 280 | + _vr->releaseFrame(); |
| 281 | +} |
0 commit comments