Skip to content

Commit

Permalink
Merge pull request #40 from geefr/fix-teleport-demo
Browse files Browse the repository at this point in the history
Interaction example and user origin fixes
  • Loading branch information
geefr authored Dec 28, 2022
2 parents 67dd61c + 0f29c18 commit c61d56c
Show file tree
Hide file tree
Showing 17 changed files with 431 additions and 78 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.vscode
build

19 changes: 14 additions & 5 deletions examples/interaction_locomotion/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ void Game::loadScene()
_sceneRoot->addChild(_controllerRight);

_teleportMarker = vsg::Switch::create();
_teleportMarker->addChild(false, teleport_marker());
auto teleportMatrix = vsg::MatrixTransform::create();
_teleportMarker->addChild(false, teleportMatrix);
teleportMatrix->addChild(teleport_marker());
_sceneRoot->addChild(_teleportMarker);

// User origin - The regular vsg scene / world space,
Expand Down Expand Up @@ -75,19 +77,26 @@ void Game::initVR()

void Game::initActions()
{
// Configure OpenXR action sets and pose bindings - These allow elements of the OpenXR device tree to be located and tracked in space,
// Tracking the location of the user's headset is achieved by tracking the VIEW reference space
// vsgvr provides a SpaceBinding class for this - Similar to the ActionPoseBindings the head's pose
// will be tracked during rendering, and available when performing interactions
_headPose = vsgvr::SpaceBinding::create(_xrInstance, XrReferenceSpaceType::XR_REFERENCE_SPACE_TYPE_VIEW);
_vr->spaceBindings.push_back(_headPose);

// Input devices are tracked via ActionPoseBindings - Tracking elements from the OpenXR device tree in the session space,
// along with binding the OpenXR input subsystem through to usable actions.
_baseActionSet = vsgvr::ActionSet::create(_xrInstance, "controller_positions", "Controller Positions");
// Pose bindings - One for each hand
// Pose bindings
_leftHandPose = vsgvr::ActionPoseBinding::create(_xrInstance, _baseActionSet, "left_hand", "Left Hand");
_rightHandPose = vsgvr::ActionPoseBinding::create(_xrInstance, _baseActionSet, "right_hand", "Right Hand");

_baseActionSet->actions = {
_leftHandPose,
_rightHandPose,
};

_interactions.emplace("teleport", new Interaction_teleport(_xrInstance, _leftHandPose, _teleportMarker, _ground));
_interactions.emplace("slide", new Interaction_slide(_xrInstance, _leftHandPose, _ground));
_interactions.emplace("teleport", new Interaction_teleport(_xrInstance, _headPose, _leftHandPose, _teleportMarker, _ground));
_interactions.emplace("slide", new Interaction_slide(_xrInstance, _headPose, _leftHandPose, _ground));

// Ask OpenXR to suggest interaction bindings.
// * If subpaths are used, list all paths that each action should be bound for
Expand Down
2 changes: 2 additions & 0 deletions examples/interaction_locomotion/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <vsgvr/actions/Action.h>
#include <vsgvr/actions/ActionSet.h>
#include <vsgvr/actions/ActionPoseBinding.h>
#include <vsgvr/actions/SpaceBinding.h>

#include "interaction.h"

Expand Down Expand Up @@ -48,6 +49,7 @@ class Game {
vsg::ref_ptr<vsgvr::ActionSet> _baseActionSet;
vsg::ref_ptr<vsgvr::ActionPoseBinding> _leftHandPose;
vsg::ref_ptr<vsgvr::ActionPoseBinding> _rightHandPose;
vsg::ref_ptr<vsgvr::SpaceBinding> _headPose;

std::map<std::string, std::unique_ptr<Interaction>> _interactions;

Expand Down
1 change: 1 addition & 0 deletions examples/interaction_locomotion/interaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <vsgvr/actions/Action.h>
#include <vsgvr/actions/ActionSet.h>
#include <vsgvr/actions/ActionPoseBinding.h>
#include <vsgvr/actions/SpaceBinding.h>

#include <list>
#include <map>
Expand Down
40 changes: 36 additions & 4 deletions examples/interaction_locomotion/interactions/interaction_slide.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
#include <iostream>

Interaction_slide::Interaction_slide(
vsg::ref_ptr<vsgvr::Instance> xrInstance,
vsg::ref_ptr<vsgvr::Instance> xrInstance,
vsg::ref_ptr<vsgvr::SpaceBinding> headPose,
vsg::ref_ptr<vsgvr::ActionPoseBinding> leftHandPose,
vsg::ref_ptr<vsg::Group> ground)
: _leftHandPose(leftHandPose)
: _headPose(headPose)
, _leftHandPose(leftHandPose)
, _ground(ground)
{
_actionSet = vsgvr::ActionSet::create(xrInstance, "slide", "Slide");
Expand Down Expand Up @@ -45,18 +47,36 @@ Interaction_slide::~Interaction_slide() {}

void Interaction_slide::frame(vsg::ref_ptr<vsg::Group> scene, Game& game, double deltaT)
{
bool updateOrigin = false;

// The player's position in vr space, at 'ground' level
// In most cases this will simply be playerPosOrigin.z = 0,
// though an intersection to the scene would be more appropriate in many cases
auto playerPosUser = _headPose->getTransform() * vsg::dvec3{ 0.0, 0.0, 0.0 };
playerPosUser.z = 0.0;

// Player's position in the scene - Strafing is performed in scene space
auto newPositionScene = game.userOrigin()->userToScene() * playerPosUser;

if (_rotateAction->getStateValid())
{
auto state = _rotateAction->getStateFloat();
if (state.isActive && fabs(state.currentState) > deadZone)
{
_rotation += state.currentState * rotateSensitivity * deltaT * -1.0;
game.userOrigin()->orientation = vsg::dquat(_rotation, { 0.0, 0.0, 1.0 });
updateOrigin = true;
}
}

if (_leftHandPose->getTransformValid() && _strafeXAction->getStateValid() && _strafeYAction->getStateValid())
{
// Direction vectors in scene space, based on left controller
// (User may point controller to modify strafe direction)
// Alternatively the head's pose could be used to determine forward,
// but that would prevent strafing sideways while looking sideways
//
// Here all strafing is performed in the xy plane, ignoring height differences
// of the ground.
auto lt = game.userOrigin()->userToScene() * _leftHandPose->getTransform();
auto lForward = (lt * vsg::dvec3{0.0, 0.0, -1.0}) - (lt * vsg::dvec3{0.0, 0.0, 0.0});
lForward.z = 0;
Expand All @@ -73,8 +93,20 @@ void Interaction_slide::frame(vsg::ref_ptr<vsg::Group> scene, Game& game, double
if (vsg::length(d) > deadZone)
{
d *= strafeSensitivity * deltaT;
game.userOrigin()->position += d;
newPositionScene += d;
updateOrigin = true;
}
}
}

if( updateOrigin )
{
// Update the vsg scene's transform to strafe player as needed
game.userOrigin()->setUserInScene(
playerPosUser,
newPositionScene,
vsg::dquat(_rotation, { 0.0, 0.0, 1.0 }),
{ 1.0, 1.0, 1.0 }
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ class Interaction_slide : public Interaction
{
public:
Interaction_slide() = delete;
Interaction_slide(vsg::ref_ptr<vsgvr::Instance> xrInstance,
Interaction_slide(vsg::ref_ptr<vsgvr::Instance> xrInstance,
vsg::ref_ptr<vsgvr::SpaceBinding> headPose,
vsg::ref_ptr<vsgvr::ActionPoseBinding> leftHandPose,
vsg::ref_ptr<vsg::Group> ground);

Expand All @@ -24,6 +25,7 @@ class Interaction_slide : public Interaction
vsg::ref_ptr<vsgvr::Action> _strafeXAction;
vsg::ref_ptr<vsgvr::Action> _strafeYAction;
vsg::ref_ptr<vsgvr::Action> _rotateAction;
vsg::ref_ptr<vsgvr::SpaceBinding> _headPose;
vsg::ref_ptr<vsgvr::ActionPoseBinding> _leftHandPose;

vsg::ref_ptr<vsg::Group> _ground;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
#include <iostream>

Interaction_teleport::Interaction_teleport(vsg::ref_ptr<vsgvr::Instance> xrInstance,
vsg::ref_ptr<vsgvr::SpaceBinding> headPose,
vsg::ref_ptr<vsgvr::ActionPoseBinding> leftHandPose,
vsg::ref_ptr<vsg::Switch> teleportTarget,
vsg::ref_ptr<vsg::Group> ground)
: _leftHandPose(leftHandPose)
: _headPose(headPose)
, _leftHandPose(leftHandPose)
, _teleportTarget(teleportTarget)
, _ground(ground)
{
Expand Down Expand Up @@ -42,6 +44,9 @@ Interaction_teleport::~Interaction_teleport() {}

void Interaction_teleport::frame(vsg::ref_ptr<vsg::Group> scene, Game& game, double deltaT)
{
auto applyRotation = false;
auto applyTeleport = false;

if (_teleportAction->getStateValid())
{
auto state = _teleportAction->getStateBool();
Expand All @@ -50,6 +55,7 @@ void Interaction_teleport::frame(vsg::ref_ptr<vsg::Group> scene, Game& game, dou
_teleportButtonDown = true;
}
}

double rotThresh = 0.25;
if (_rotateAction->getStateValid())
{
Expand All @@ -62,11 +68,11 @@ void Interaction_teleport::frame(vsg::ref_ptr<vsg::Group> scene, Game& game, dou
if (_rotateActionState != 0)
{
_playerRotation += (- 15.0 * _rotateActionState);
game.userOrigin()->orientation = vsg::dquat(vsg::radians(_playerRotation), { 0.0, 0.0, 1.0 });
applyRotation = true;
}
}
}

if (_teleportButtonDown && _leftHandPose->getTransformValid())
{
// Raycast from controller aim to world, colliding with anything named "ground"
Expand All @@ -79,8 +85,8 @@ void Interaction_teleport::frame(vsg::ref_ptr<vsg::Group> scene, Game& game, dou

auto intersector = vsg::LineSegmentIntersector::create(intersectStart, intersectEnd);
// TODO: Intersect whole scene, or sub-scene matched on tags? For now we can't be anywhere but the ground plane
// TODO: Intersections appear to be in reverse-distance order. Should double check this but intersections.back()
// seems to always get the ground plane (and not fences, trees, houses, or the underside of the ground plane.
// TODO: Should sort intersections as well - For now intersections.back() seems to always get the ground plane,
// and not other geometry within the world.
_ground->accept(*intersector);
if (!intersector->intersections.empty())
{
Expand All @@ -93,7 +99,7 @@ void Interaction_teleport::frame(vsg::ref_ptr<vsg::Group> scene, Game& game, dou
{
if (auto m = child.node->cast<vsg::MatrixTransform>())
{
m->matrix = vsg::translate(_teleportPosition) * vsg::rotate(vsg::radians(90.0), {1.0, 0.0, 0.0});
m->matrix = vsg::translate(_teleportPosition);
}
}
}
Expand All @@ -112,7 +118,8 @@ void Interaction_teleport::frame(vsg::ref_ptr<vsg::Group> scene, Game& game, dou
if (_teleportTargetValid)
{
// Teleport position within the child scene
game.userOrigin()->position = game.userOrigin()->userToScene() * _teleportPosition;
// Here the origin is being moved, by the difference between the teleport target, and where the user currently is (left hand)
applyTeleport = true;
}
_teleportButtonDown = false;
_teleportTargetValid = false;
Expand All @@ -127,4 +134,34 @@ void Interaction_teleport::frame(vsg::ref_ptr<vsg::Group> scene, Game& game, dou
if( fabs(state.currentState) < rotThresh) _rotateActionState = 0;
}
}

if( applyRotation || applyTeleport )
{
// The player's position in vr space, at 'ground' level
// In this case we know the level if the ground from the teleport target
auto playerPosOrigin = _headPose->getTransform() * vsg::dvec3{ 0.0, 0.0, 0.0 };
playerPosOrigin.z = _teleportPosition.z;

vsg::dvec3 newPlayerPosScene;
if (!applyTeleport)
{
// The player hasn't moved
newPlayerPosScene = game.userOrigin()->userToScene() * playerPosOrigin;
}
else
{
// The player has moved - Center on the teleport target
newPlayerPosScene = game.userOrigin()->userToScene() * _teleportPosition;
}

// Set the transform from vr origin to position/rotation within scene
// The initial translate is important as the user is rarely positioned
// at the origin of their vr space.
game.userOrigin()->setUserInScene(
playerPosOrigin,
newPlayerPosScene,
vsg::dquat(vsg::radians(_playerRotation), { 0.0, 0.0, 1.0 }),
{1.0, 1.0, 1.0}
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Interaction_teleport : public Interaction
public:
Interaction_teleport() = delete;
Interaction_teleport(vsg::ref_ptr<vsgvr::Instance> xrInstance,
vsg::ref_ptr<vsgvr::SpaceBinding> headPose,
vsg::ref_ptr<vsgvr::ActionPoseBinding> leftHandPose,
vsg::ref_ptr<vsg::Switch> teleportTarget,
vsg::ref_ptr<vsg::Group> ground);
Expand All @@ -23,11 +24,13 @@ class Interaction_teleport : public Interaction

vsg::dvec3 _teleportPosition = {0.0, 0.0, 0.0};
vsg::ref_ptr<vsg::Switch> _teleportTarget;
vsg::ref_ptr<vsgvr::SpaceBinding> _headPose;
vsg::ref_ptr<vsgvr::ActionPoseBinding> _leftHandPose;

bool _teleportButtonDown = false;
bool _teleportTargetValid = false;
int _rotateActionState = 0; // -1 is rot left, 1 is rot right

double _playerRotation = 0.0;

vsg::ref_ptr<vsg::Group> _ground;
Expand Down
2 changes: 2 additions & 0 deletions vsgvr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
set( HEADERS_ACTIONS
include/vsgvr/actions/Action.h
include/vsgvr/actions/ActionPoseBinding.h
include/vsgvr/actions/SpaceBinding.h
include/vsgvr/actions/ActionSet.h
)

Expand All @@ -25,6 +26,7 @@ set( HEADERS_XR
set( SOURCES
src/vsgvr/actions/Action.cpp
src/vsgvr/actions/ActionPoseBinding.cpp
src/vsgvr/actions/SpaceBinding.cpp
src/vsgvr/actions/ActionSet.cpp
src/vsgvr/app/Viewer.cpp
src/vsgvr/app/UserOrigin.cpp
Expand Down
62 changes: 62 additions & 0 deletions vsgvr/include/vsgvr/actions/SpaceBinding.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
Copyright(c) 2022 Gareth Francis
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#pragma once

#include <vsg/core/Inherit.h>

#include <vsgvr/xr/Common.h>
#include <vsgvr/xr/Instance.h>

namespace vsgvr {
class Session;

/**
* A binding class allowing an XrSpace to be tracked by the Viewer.
* The specified reference space will be kept updated during rendering (along with any ActionPoseBindings)
* in order to track elements such as the position of the headset (view) space, or user's local space.
*/
class VSGVR_DECLSPEC SpaceBinding : public vsg::Inherit<vsg::Object, SpaceBinding>
{
public:
SpaceBinding(vsg::ref_ptr<Instance> instance, XrReferenceSpaceType spaceType);
virtual ~SpaceBinding();

XrSpace getSpace() const { return _space; }

bool getTransformValid() const { return _transformValid; }
vsg::dmat4 getTransform() const { return _transform; }
XrReferenceSpaceType getSpaceType() const { return _spaceType; }

void createSpace(Session* session);
void destroySpace();

void setSpaceLocation(XrSpaceLocation location);
private:
XrSpace _space = XR_NULL_HANDLE;
XrReferenceSpaceType _spaceType;

bool _transformValid = false;
vsg::dmat4 _transform;
};
}

EVSG_type_name(vsgvr::SpaceBinding);
Loading

0 comments on commit c61d56c

Please sign in to comment.